Debugger: GB - Added sprite viewer

This commit is contained in:
Sour 2020-06-01 23:36:18 -04:00
parent 6bccfa874f
commit 4db6c08822
8 changed files with 151 additions and 21 deletions

View file

@ -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;
}
}
}
}
}

View file

@ -31,4 +31,5 @@ public:
void UpdateViewers(uint16_t scanline, uint16_t cycle); void UpdateViewers(uint16_t scanline, uint16_t cycle);
void GetGameboyTilemap(uint8_t* vram, GbPpuState& state, uint16_t offset, uint32_t* outBuffer); 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);
}; };

View file

@ -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 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 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 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); } DllExport uint32_t __stdcall GetDebugEventCount(EventViewerDisplayOptions options) { return GetDebugger()->GetEventManager()->GetEventCount(options); }

View file

@ -25,6 +25,8 @@ namespace Mesen.GUI.Debugger.PpuViewer
public bool VerticalMirror; public bool VerticalMirror;
public bool UseSecondTable; public bool UseSecondTable;
private bool GameboyMode;
public Rectangle GetBounds() public Rectangle GetBounds()
{ {
return new Rectangle(X, Y, Width, Height); return new Rectangle(X, Y, Width, Height);
@ -32,6 +34,10 @@ namespace Mesen.GUI.Debugger.PpuViewer
public bool IsVisible() public bool IsVisible()
{ {
if(GameboyMode) {
return !(X == 0 || X >= 168 || Y+Height <= 16 || Y >= 160);
}
if(X + Width <= 0 || X > 255) { if(X + Width <= 0 || X > 255) {
return false; return false;
} }
@ -44,6 +50,33 @@ namespace Mesen.GUI.Debugger.PpuViewer
return true; 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) public static SpriteInfo GetSpriteInfo(byte[] oamRam, int oamMode, int spriteIndex)
{ {
SpriteInfo sprite = new SpriteInfo(); SpriteInfo sprite = new SpriteInfo();

View file

@ -16,8 +16,10 @@ namespace Mesen.GUI.Debugger.PpuViewer
public event SpriteSelectedHandler SpriteSelected; public event SpriteSelectedHandler SpriteSelected;
private List<SpriteInfo> _sprites; private List<SpriteInfo> _sprites;
private DebugState _state;
private byte[] _oamRam; private byte[] _oamRam;
private int _oamMode; private int _oamMode;
private bool _isGameboyMode;
private int _selectedIndex; private int _selectedIndex;
private int _sortOrder = 1; private int _sortOrder = 1;
private DateTime _lastRefresh = DateTime.MinValue; 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) { if(_oamRam == oamRam) {
return; return;
} }
_state = state;
_oamRam = oamRam; _oamRam = oamRam;
_oamMode = oamMode; _oamMode = oamMode;
_isGameboyMode = gameboyMode;
if((DateTime.Now - _lastRefresh).TotalMilliseconds > 200) { if((DateTime.Now - _lastRefresh).TotalMilliseconds > 200) {
RefreshList(); RefreshList();
} }
@ -79,8 +83,14 @@ namespace Mesen.GUI.Debugger.PpuViewer
private void RefreshList() private void RefreshList()
{ {
List<SpriteInfo> sprites = new List<SpriteInfo>(); List<SpriteInfo> sprites = new List<SpriteInfo>();
for(int i = 0; i < 128; i++) { for(int i = 0; i < (_isGameboyMode ? 40 : 128); i++) {
SpriteInfo sprite = SpriteInfo.GetSpriteInfo(_oamRam, _oamMode, 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()) { if(!chkHideOffscreenSprites.Checked || sprite.IsVisible()) {
sprites.Add(sprite); sprites.Add(sprite);
} }

View file

@ -18,7 +18,7 @@ namespace Mesen.GUI.Debugger
{ {
public partial class frmSpriteViewer : BaseForm, IRefresh public partial class frmSpriteViewer : BaseForm, IRefresh
{ {
private PpuState _state; private DebugState _state;
private byte[] _vram; private byte[] _vram;
private byte[] _cgram; private byte[] _cgram;
private byte[] _oamRam; private byte[] _oamRam;
@ -26,6 +26,7 @@ namespace Mesen.GUI.Debugger
private Bitmap _previewImage; private Bitmap _previewImage;
private GetSpritePreviewOptions _options = new GetSpritePreviewOptions(); private GetSpritePreviewOptions _options = new GetSpritePreviewOptions();
private WindowRefreshManager _refreshManager; private WindowRefreshManager _refreshManager;
private bool _isGameboyMode = false;
public ctrlScanlineCycleSelect ScanlineCycleSelect { get { return this.ctrlScanlineCycleSelect; } } public ctrlScanlineCycleSelect ScanlineCycleSelect { get { return this.ctrlScanlineCycleSelect; } }
@ -41,11 +42,6 @@ namespace Mesen.GUI.Debugger
return; return;
} }
_previewData = new byte[256 * 240 * 4];
_previewImage = new Bitmap(256, 240, PixelFormat.Format32bppPArgb);
ctrlImagePanel.ImageSize = new Size(256, 240);
ctrlImagePanel.Image = _previewImage;
InitShortcuts(); InitShortcuts();
SpriteViewerConfig config = ConfigManager.Config.Debug.SpriteViewer; SpriteViewerConfig config = ConfigManager.Config.Debug.SpriteViewer;
@ -102,27 +98,40 @@ namespace Mesen.GUI.Debugger
public void RefreshData() public void RefreshData()
{ {
_state = DebugApi.GetState().Ppu; _isGameboyMode = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy;
_vram = DebugApi.GetMemoryState(SnesMemoryType.VideoRam); _state = DebugApi.GetState();
_oamRam = DebugApi.GetMemoryState(SnesMemoryType.SpriteRam); _vram = DebugApi.GetMemoryState(_isGameboyMode ? SnesMemoryType.GbVideoRam : SnesMemoryType.VideoRam);
_oamRam = DebugApi.GetMemoryState(_isGameboyMode ? SnesMemoryType.GbSpriteRam : SnesMemoryType.SpriteRam);
_cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam); _cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam);
} }
public void RefreshViewer() 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)) { using(Graphics g = Graphics.FromImage(_previewImage)) {
GCHandle handle = GCHandle.Alloc(_previewData, GCHandleType.Pinned); 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); g.DrawImage(source, 0, 0);
handle.Free(); handle.Free();
} }
if(_options.SelectedSprite >= 0) { if(_options.SelectedSprite >= 0) {
ctrlImagePanel.Selection = SpriteInfo.GetSpriteInfo(_oamRam, _state.OamMode, _options.SelectedSprite).GetBounds(); ctrlImagePanel.Selection = GetSpriteInfo(_options.SelectedSprite).GetBounds();
} else { } else {
ctrlImagePanel.Selection = Rectangle.Empty; ctrlImagePanel.Selection = Rectangle.Empty;
} }
@ -157,8 +166,8 @@ namespace Mesen.GUI.Debugger
int y = e.Y / ctrlImagePanel.ImageScale; int y = e.Y / ctrlImagePanel.ImageScale;
SpriteInfo match = null; SpriteInfo match = null;
for(int i = 0; i < 128; i++) { for(int i = 0; i < (_isGameboyMode ? 40 : 128); i++) {
SpriteInfo sprite = SpriteInfo.GetSpriteInfo(_oamRam, _state.OamMode, i); SpriteInfo sprite = GetSpriteInfo(i);
if(x >= sprite.X && x <= sprite.X + sprite.Width) { if(x >= sprite.X && x <= sprite.X + sprite.Width) {
int endY = (sprite.Y + sprite.Height) & 0xFF; int endY = (sprite.Y + sprite.Height) & 0xFF;
bool visible = (y >= sprite.Y && y < endY) || (endY < sprite.Y && y < endY); bool visible = (y >= sprite.Y && y < endY) || (endY < sprite.Y && y < endY);
@ -172,6 +181,18 @@ namespace Mesen.GUI.Debugger
SelectSprite(match); 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) private void SelectSprite(SpriteInfo sprite)
{ {
if(sprite != null) { if(sprite != null) {

View file

@ -481,8 +481,6 @@ namespace Mesen.GUI.Forms
mnuDebugger.Visible = !isGameboyMode; mnuDebugger.Visible = !isGameboyMode;
mnuSpcDebugger.Enabled = !isGameboyMode; mnuSpcDebugger.Enabled = !isGameboyMode;
mnuSpcDebugger.Visible = !isGameboyMode; mnuSpcDebugger.Visible = !isGameboyMode;
mnuSpriteViewer.Enabled = !isGameboyMode;
mnuSpriteViewer.Visible = !isGameboyMode;
mnuAssembler.Enabled = !isGameboyMode; mnuAssembler.Enabled = !isGameboyMode;
mnuAssembler.Visible = !isGameboyMode; mnuAssembler.Visible = !isGameboyMode;
sepCoprocessors.Visible = !isGameboyMode; sepCoprocessors.Visible = !isGameboyMode;

View file

@ -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 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 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 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)] public static extern void SetViewerUpdateTiming(Int32 viewerId, Int32 scanline, Int32 cycle);
[DllImport(DllPath)] private static extern UInt32 GetDebugEventCount(EventViewerDisplayOptions options); [DllImport(DllPath)] private static extern UInt32 GetDebugEventCount(EventViewerDisplayOptions options);