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

View file

@ -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();

View file

@ -16,8 +16,10 @@ namespace Mesen.GUI.Debugger.PpuViewer
public event SpriteSelectedHandler SpriteSelected;
private List<SpriteInfo> _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<SpriteInfo> sprites = new List<SpriteInfo>();
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);
}

View file

@ -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) {

View file

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

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 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);