diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 810c8a303b..d054ab86f6 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -921,8 +921,8 @@ namespace BizHawk.Client.Common if (!Global.Config.GB_AsSGB) { //core = CoreInventory.Instance["GB", "Pizza Boy"]; - //core = CoreInventory.Instance["GB", "Gambatte"]; - core = CoreInventory.Instance["GB", "SameBoy"]; + core = CoreInventory.Instance["GB", "Gambatte"]; + //core = CoreInventory.Instance["GB", "SameBoy"]; } else { diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 2274b6ce5e..611c693e20 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -33,6 +33,7 @@ using BizHawk.Emulation.Common.Base_Implementations; using BizHawk.Emulation.Cores.Nintendo.SNES9X; using BizHawk.Emulation.Cores.Consoles.SNK; using BizHawk.Emulation.Cores.Consoles.Sega.PicoDrive; +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; namespace BizHawk.Client.EmuHawk { @@ -1744,6 +1745,10 @@ namespace BizHawk.Client.EmuHawk { sNESToolStripMenuItem.Visible = true; } + else if (Emulator is Sameboy) + { + GBSubMenu.Visible = true; + } break; case "Coleco": ColecoSubMenu.Visible = true; diff --git a/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs b/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs index 6151c05677..8dc50567ef 100644 --- a/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs +++ b/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs @@ -9,13 +9,15 @@ using BizHawk.Emulation.Cores.Nintendo.Gameboy; using BizHawk.Client.EmuHawk.WinFormExtensions; using System.Collections.Generic; using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; +using BizHawk.Common; namespace BizHawk.Client.EmuHawk { public partial class GBGPUView : Form, IToolFormAutoConfig { [RequiredService] - public Gameboy Gb { get; private set; } + public IGameboyCommon Gb { get; private set; } // TODO: freeze semantics are a bit weird: details for a mouseover or freeze are taken from the current // state, not the state at the last callback (and so can be quite different when update is set to manual). @@ -32,11 +34,8 @@ namespace BizHawk.Client.EmuHawk // g' = 8.25g // b' = 8.25b - // gambatte doesn't modify these memory locations unless you reconstruct, so we can store - private IntPtr _vram; - private IntPtr _bgpal; - private IntPtr _sppal; - private IntPtr _oam; + + private GPUMemoryAreas _memory; private bool _cgb; // set once at start private int _lcdc; // set at each callback @@ -89,13 +88,9 @@ namespace BizHawk.Client.EmuHawk _cgb = Gb.IsCGBMode(); _lcdc = 0; - // TODO: can this be a required Emulator Service, and let the tool manage the logic of closing? - if (!Gb.GetGPUMemoryAreas(out _vram, out _bgpal, out _sppal, out _oam)) - { - if (Visible) - Close(); - } - tilespal = _bgpal; + _memory = Gb.GetGPU(); + + tilespal = _memory.Bgpal; if (_cgb) label4.Enabled = true; @@ -357,112 +352,120 @@ namespace BizHawk.Client.EmuHawk #endregion - void ScanlineCallback(int lcdc) + void ScanlineCallback(byte lcdc) { - _lcdc = lcdc; - // set alpha on all pixels - unsafe + using (_memory.EnterExit()) { - int* p = (int*)_bgpal; - for (int i = 0; i < 32; i++) - p[i] |= unchecked((int)0xff000000); - p = (int*)_sppal; - for (int i = 0; i < 32; i++) - p[i] |= unchecked((int)0xff000000); - int c = Spriteback.ToArgb(); - for (int i = 0; i < 32; i += 4) - p[i] = c; - } + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - // bg maps - if (!_cgb) - { - DrawBGDMG( - bmpViewBG.BMP, - _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), - _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), - !lcdc.Bit(4), - _bgpal); + _lcdc = lcdc; + // set alpha on all pixels + // TODO: RE: Spriteback, you can't muck with Sameboy in this way due to how SGB reads stuff...? + /*unsafe + { + int* p = (int*)_bgpal; + for (int i = 0; i < 32; i++) + p[i] |= unchecked((int)0xff000000); + p = (int*)_sppal; + for (int i = 0; i < 32; i++) + p[i] |= unchecked((int)0xff000000); + int c = Spriteback.ToArgb(); + for (int i = 0; i < 32; i += 4) + p[i] = c; + }*/ - DrawBGDMG( - bmpViewWin.BMP, - _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), - _vram + 0x1000, // force win to second tile bank??? - true, - _bgpal); - } - else - { - DrawBGCGB( - bmpViewBG.BMP, - _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), - _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), - !lcdc.Bit(4), - _bgpal); + // bg maps + if (!_cgb) + { + DrawBGDMG( + bmpViewBG.BMP, + _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), + _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), + !lcdc.Bit(4), + _bgpal); - DrawBGCGB( - bmpViewWin.BMP, - _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), - _vram + 0x1000, // force win to second tile bank??? - true, - _bgpal); - } - bmpViewBG.Refresh(); - bmpViewWin.Refresh(); + DrawBGDMG( + bmpViewWin.BMP, + _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), + _vram + 0x1000, // force win to second tile bank??? + true, + _bgpal); + } + else + { + DrawBGCGB( + bmpViewBG.BMP, + _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), + _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), + !lcdc.Bit(4), + _bgpal); - // tile display - // TODO: user selects palette to use, instead of fixed palette 0 - // or possibly "smart" where, if a tile is in use, it's drawn with one of the palettes actually being used with it? - DrawTiles(bmpViewTiles1.BMP, _vram, tilespal); - bmpViewTiles1.Refresh(); - if (_cgb) - { - DrawTiles(bmpViewTiles2.BMP, _vram + 0x2000, tilespal); - bmpViewTiles2.Refresh(); - } + DrawBGCGB( + bmpViewWin.BMP, + _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), + _vram + 0x1000, // force win to second tile bank??? + true, + _bgpal); + } + bmpViewBG.Refresh(); + bmpViewWin.Refresh(); - // palettes - if (_cgb) - { - bmpViewBGPal.ChangeBitmapSize(8, 4); - if (bmpViewBGPal.Width != 128) - bmpViewBGPal.Width = 128; - bmpViewSPPal.ChangeBitmapSize(8, 4); - if (bmpViewSPPal.Width != 128) - bmpViewSPPal.Width = 128; - DrawPal(bmpViewBGPal.BMP, _bgpal, 8); - DrawPal(bmpViewSPPal.BMP, _sppal, 8); - } - else - { - bmpViewBGPal.ChangeBitmapSize(1, 4); - if (bmpViewBGPal.Width != 16) - bmpViewBGPal.Width = 16; - bmpViewSPPal.ChangeBitmapSize(2, 4); - if (bmpViewSPPal.Width != 32) - bmpViewSPPal.Width = 32; - DrawPal(bmpViewBGPal.BMP, _bgpal, 1); - DrawPal(bmpViewSPPal.BMP, _sppal, 2); - } - bmpViewBGPal.Refresh(); - bmpViewSPPal.Refresh(); + // tile display + // TODO: user selects palette to use, instead of fixed palette 0 + // or possibly "smart" where, if a tile is in use, it's drawn with one of the palettes actually being used with it? + DrawTiles(bmpViewTiles1.BMP, _vram, tilespal); + bmpViewTiles1.Refresh(); + if (_cgb) + { + DrawTiles(bmpViewTiles2.BMP, _vram + 0x2000, tilespal); + bmpViewTiles2.Refresh(); + } - // oam - if (lcdc.Bit(2)) // 8x16 - { - bmpViewOAM.ChangeBitmapSize(320, 16); - if (bmpViewOAM.Height != 16) - bmpViewOAM.Height = 16; - } - else - { - bmpViewOAM.ChangeBitmapSize(320, 8); - if (bmpViewOAM.Height != 8) - bmpViewOAM.Height = 8; - } - DrawOam(bmpViewOAM.BMP, _oam, _vram, _sppal, lcdc.Bit(2), _cgb); - bmpViewOAM.Refresh(); + // palettes + if (_cgb) + { + bmpViewBGPal.ChangeBitmapSize(8, 4); + if (bmpViewBGPal.Width != 128) + bmpViewBGPal.Width = 128; + bmpViewSPPal.ChangeBitmapSize(8, 4); + if (bmpViewSPPal.Width != 128) + bmpViewSPPal.Width = 128; + DrawPal(bmpViewBGPal.BMP, _bgpal, 8); + DrawPal(bmpViewSPPal.BMP, _sppal, 8); + } + else + { + bmpViewBGPal.ChangeBitmapSize(1, 4); + if (bmpViewBGPal.Width != 16) + bmpViewBGPal.Width = 16; + bmpViewSPPal.ChangeBitmapSize(2, 4); + if (bmpViewSPPal.Width != 32) + bmpViewSPPal.Width = 32; + DrawPal(bmpViewBGPal.BMP, _bgpal, 1); + DrawPal(bmpViewSPPal.BMP, _sppal, 2); + } + bmpViewBGPal.Refresh(); + bmpViewSPPal.Refresh(); + // oam + if (lcdc.Bit(2)) // 8x16 + { + bmpViewOAM.ChangeBitmapSize(320, 16); + if (bmpViewOAM.Height != 16) + bmpViewOAM.Height = 16; + } + else + { + bmpViewOAM.ChangeBitmapSize(320, 8); + if (bmpViewOAM.Height != 8) + bmpViewOAM.Height = 8; + } + DrawOam(bmpViewOAM.BMP, _oam, _vram, _sppal, lcdc.Bit(2), _cgb); + bmpViewOAM.Refresh(); + } // try to run the current mouseover, to refresh if the mouse is being held over a pane while the emulator runs // this doesn't really work well; the update rate seems to be throttled MouseEventArgs e = new MouseEventArgs(MouseButtons.None, 0, Cursor.Position.X, Cursor.Position.Y, 0); @@ -514,7 +517,7 @@ namespace BizHawk.Client.EmuHawk private void buttonRefresh_Click(object sender, EventArgs e) { - if (cbscanline == -2 && Gb != null) + if (cbscanline == -2) Gb.SetScanlineCallback(ScanlineCallback, -2); } @@ -614,145 +617,177 @@ namespace BizHawk.Client.EmuHawk private unsafe void PaletteMouseover(int x, int y, bool sprite) { - bmpViewDetails.ChangeBitmapSize(8, 10); - if (bmpViewDetails.Height != 80) - bmpViewDetails.Height = 80; - var sb = new StringBuilder(); - x /= 16; - y /= 16; - int* pal = (int*)(sprite ? _sppal : _bgpal) + x * 4; - int color = pal[y]; - - sb.AppendLine(string.Format("Palette {0}", x)); - sb.AppendLine(string.Format("Color {0}", y)); - sb.AppendLine(string.Format("(R,G,B) = ({0},{1},{2})", color >> 16 & 255, color >> 8 & 255, color & 255)); - - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 10), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - int* dest = (int*)lockdata.Scan0; - int pitch = lockdata.Stride / sizeof(int); - - for (int py = 0; py < 10; py++) + using (_memory.EnterExit()) { - for (int px = 0; px < 8; px++) + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; + + bmpViewDetails.ChangeBitmapSize(8, 10); + if (bmpViewDetails.Height != 80) + bmpViewDetails.Height = 80; + var sb = new StringBuilder(); + x /= 16; + y /= 16; + int* pal = (int*)(sprite ? _sppal : _bgpal) + x * 4; + int color = pal[y]; + + sb.AppendLine(string.Format("Palette {0}", x)); + sb.AppendLine(string.Format("Color {0}", y)); + sb.AppendLine(string.Format("(R,G,B) = ({0},{1},{2})", color >> 16 & 255, color >> 8 & 255, color & 255)); + + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 10), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + int* dest = (int*)lockdata.Scan0; + int pitch = lockdata.Stride / sizeof(int); + + for (int py = 0; py < 10; py++) { - if (py < 8) - *dest++ = color; - else - *dest++ = pal[px / 2]; + for (int px = 0; px < 8; px++) + { + if (py < 8) + *dest++ = color; + else + *dest++ = pal[px / 2]; + } + dest -= 8; + dest += pitch; } - dest -= 8; - dest += pitch; + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } unsafe void TileMouseover(int x, int y, bool secondbank) { - // todo: draw with a specific palette - bmpViewDetails.ChangeBitmapSize(8, 8); - if (bmpViewDetails.Height != 64) - bmpViewDetails.Height = 64; - var sb = new StringBuilder(); - x /= 8; - y /= 8; - int tileindex = y * 16 + x; - int tileoffs = tileindex * 16; - if (_cgb) - sb.AppendLine(string.Format("Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, secondbank ? 1 : 0)); - else - sb.AppendLine(string.Format("Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + using (_memory.EnterExit()) + { + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - DrawTile((byte*)_vram + tileoffs + (secondbank ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)tilespal); - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); + // todo: draw with a specific palette + bmpViewDetails.ChangeBitmapSize(8, 8); + if (bmpViewDetails.Height != 64) + bmpViewDetails.Height = 64; + var sb = new StringBuilder(); + x /= 8; + y /= 8; + int tileindex = y * 16 + x; + int tileoffs = tileindex * 16; + if (_cgb) + sb.AppendLine(string.Format("Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, secondbank ? 1 : 0)); + else + sb.AppendLine(string.Format("Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + DrawTile((byte*)_vram + tileoffs + (secondbank ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)tilespal); + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); + } } unsafe void TilemapMouseover(int x, int y, bool win) { - bmpViewDetails.ChangeBitmapSize(8, 8); - if (bmpViewDetails.Height != 64) - bmpViewDetails.Height = 64; - var sb = new StringBuilder(); - bool secondmap = win ? _lcdc.Bit(6) : _lcdc.Bit(3); - int mapoffs = secondmap ? 0x1c00 : 0x1800; - x /= 8; - y /= 8; - mapoffs += y * 32 + x; - byte* mapbase = (byte*)_vram + mapoffs; - int tileindex = mapbase[0]; - if (win || !_lcdc.Bit(4)) // 0x9000 base - if (tileindex < 128) - tileindex += 256; // compute all if from 0x8000 base - int tileoffs = tileindex * 16; - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - if (!_cgb) + using (_memory.EnterExit()) { - sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); - sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); - DrawTile((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal); - } - else - { - int tileext = mapbase[8192]; + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); - sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, tileext.Bit(3) ? 1 : 0)); - sb.AppendLine(string.Format(" Palette {0}", tileext & 7)); - sb.AppendLine(string.Format(" Flags {0}{1}{2}", tileext.Bit(5) ? 'H' : ' ', tileext.Bit(6) ? 'V' : ' ', tileext.Bit(7) ? 'P' : ' ')); - DrawTileHv((byte*)_vram + tileoffs + (tileext.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal + 4 * (tileext & 7), tileext.Bit(5), tileext.Bit(6)); + bmpViewDetails.ChangeBitmapSize(8, 8); + if (bmpViewDetails.Height != 64) + bmpViewDetails.Height = 64; + var sb = new StringBuilder(); + bool secondmap = win ? _lcdc.Bit(6) : _lcdc.Bit(3); + int mapoffs = secondmap ? 0x1c00 : 0x1800; + x /= 8; + y /= 8; + mapoffs += y * 32 + x; + byte* mapbase = (byte*)_vram + mapoffs; + int tileindex = mapbase[0]; + if (win || !_lcdc.Bit(4)) // 0x9000 base + if (tileindex < 128) + tileindex += 256; // compute all if from 0x8000 base + int tileoffs = tileindex * 16; + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + if (!_cgb) + { + sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); + sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + DrawTile((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal); + } + else + { + int tileext = mapbase[8192]; + + sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); + sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, tileext.Bit(3) ? 1 : 0)); + sb.AppendLine(string.Format(" Palette {0}", tileext & 7)); + sb.AppendLine(string.Format(" Flags {0}{1}{2}", tileext.Bit(5) ? 'H' : ' ', tileext.Bit(6) ? 'V' : ' ', tileext.Bit(7) ? 'P' : ' ')); + DrawTileHv((byte*)_vram + tileoffs + (tileext.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal + 4 * (tileext & 7), tileext.Bit(5), tileext.Bit(6)); + } + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } unsafe void SpriteMouseover(int x, int y) { - bool tall = _lcdc.Bit(2); - x /= 8; - y /= 8; - bmpViewDetails.ChangeBitmapSize(8, tall ? 16 : 8); - if (bmpViewDetails.Height != bmpViewDetails.BMP.Height * 8) - bmpViewDetails.Height = bmpViewDetails.BMP.Height * 8; - var sb = new StringBuilder(); + using (_memory.EnterExit()) + { + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - byte* oament = (byte*)_oam + 4 * x; - int sy = oament[0]; - int sx = oament[1]; - int tilenum = oament[2]; - int flags = oament[3]; - bool hflip = flags.Bit(5); - bool vflip = flags.Bit(6); - if (tall) - tilenum = vflip ? tilenum | 1 : tilenum & ~1; - int tileoffs = tilenum * 16; - sb.AppendLine(string.Format("Sprite #{0} @{1:x4}", x, 4 * x + 0xfe00)); - sb.AppendLine(string.Format(" (x,y) = ({0},{1})", sx, sy)); - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, tall ? 16 : 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - if (_cgb) - { - sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000, flags.Bit(3) ? 1 : 0)); - sb.AppendLine(string.Format(" Palette {0}", flags & 7)); - DrawTileHv((byte*)_vram + tileoffs + (flags.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + bool tall = _lcdc.Bit(2); + x /= 8; + y /= 8; + bmpViewDetails.ChangeBitmapSize(8, tall ? 16 : 8); + if (bmpViewDetails.Height != bmpViewDetails.BMP.Height * 8) + bmpViewDetails.Height = bmpViewDetails.BMP.Height * 8; + var sb = new StringBuilder(); + + byte* oament = (byte*)_oam + 4 * x; + int sy = oament[0]; + int sx = oament[1]; + int tilenum = oament[2]; + int flags = oament[3]; + bool hflip = flags.Bit(5); + bool vflip = flags.Bit(6); if (tall) - DrawTileHv((byte*)_vram + (tileoffs ^ 16) + (flags.Bit(3) ? 8192 : 0), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + tilenum = vflip ? tilenum | 1 : tilenum & ~1; + int tileoffs = tilenum * 16; + sb.AppendLine(string.Format("Sprite #{0} @{1:x4}", x, 4 * x + 0xfe00)); + sb.AppendLine(string.Format(" (x,y) = ({0},{1})", sx, sy)); + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, tall ? 16 : 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + if (_cgb) + { + sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000, flags.Bit(3) ? 1 : 0)); + sb.AppendLine(string.Format(" Palette {0}", flags & 7)); + DrawTileHv((byte*)_vram + tileoffs + (flags.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + if (tall) + DrawTileHv((byte*)_vram + (tileoffs ^ 16) + (flags.Bit(3) ? 8192 : 0), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + } + else + { + sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000)); + sb.AppendLine(string.Format(" Palette {0}", flags.Bit(4) ? 1 : 0)); + DrawTileHv((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + (flags.Bit(4) ? 4 : 0), hflip, vflip); + if (tall) + DrawTileHv((byte*)_vram + (tileoffs ^ 16), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags.Bit(4) ? 4 : 0), hflip, vflip); + } + sb.AppendLine(string.Format(" Flags {0}{1}{2}", hflip ? 'H' : ' ', vflip ? 'V' : ' ', flags.Bit(7) ? 'P' : ' ')); + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - else - { - sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000)); - sb.AppendLine(string.Format(" Palette {0}", flags.Bit(4) ? 1 : 0)); - DrawTileHv((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + (flags.Bit(4) ? 4 : 0), hflip, vflip); - if (tall) - DrawTileHv((byte*)_vram + (tileoffs ^ 16), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags.Bit(4) ? 4 : 0), hflip, vflip); - } - sb.AppendLine(string.Format(" Flags {0}{1}{2}", hflip ? 'H' : ' ', vflip ? 'V' : ' ', flags.Bit(7) ? 'P' : ' ')); - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } private void bmpViewBG_MouseEnter(object sender, EventArgs e) @@ -882,9 +917,9 @@ namespace BizHawk.Client.EmuHawk else if (e.Button == MouseButtons.Left) { if (sender == bmpViewBGPal) - tilespal = _bgpal + e.X / 16 * 16; + tilespal = _memory.Bgpal + e.X / 16 * 16; else if (sender == bmpViewSPPal) - tilespal = _sppal + e.X / 16 * 16; + tilespal = _memory.Sppal + e.X / 16 * 16; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 9e04730e4a..864cdd856d 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -439,7 +439,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy #region ppudebug - public bool GetGPUMemoryAreas(out IntPtr vram, out IntPtr bgpal, out IntPtr sppal, out IntPtr oam) + public GPUMemoryAreas GetGPU() { IntPtr _vram = IntPtr.Zero; IntPtr _bgpal = IntPtr.Zero; @@ -451,23 +451,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy || !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.sppal, ref _sppal, ref unused) || !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.oam, ref _oam, ref unused)) { - vram = IntPtr.Zero; - bgpal = IntPtr.Zero; - sppal = IntPtr.Zero; - oam = IntPtr.Zero; - return false; + throw new InvalidOperationException("Unexpected error in gambatte_getmemoryarea"); } - vram = _vram; - bgpal = _bgpal; - sppal = _sppal; - oam = _oam; - return true; - } + return new GPUMemoryAreas(_vram, _oam, _sppal, _bgpal); - /// - /// - /// current value of register $ff40 (LCDC) - public delegate void ScanlineCallback(int lcdc); + } /// /// set up callback diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs index fa236b8f47..b874919ad7 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs @@ -1,4 +1,6 @@ -using System; +using BizHawk.Common; +using BizHawk.Emulation.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,8 +8,49 @@ using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { - public interface IGameboyCommon + /// + /// + /// current value of register $ff40 (LCDC) + public delegate void ScanlineCallback(byte lcdc); + + public interface IGameboyCommon : ISpecializedEmulatorService { bool IsCGBMode(); + GPUMemoryAreas GetGPU(); + + /// + /// set up callback + /// + /// scanline. -1 = end of frame, -2 = RIGHT NOW + void SetScanlineCallback(ScanlineCallback callback, int line); + } + + public class GPUMemoryAreas : IMonitor + { + public IntPtr Vram { get; } + public IntPtr Oam { get; } + public IntPtr Sppal { get; } + public IntPtr Bgpal { get; } + + private readonly IMonitor _monitor; + + public GPUMemoryAreas(IntPtr vram, IntPtr oam, IntPtr sppal, IntPtr bgpal, IMonitor monitor = null) + { + Vram = vram; + Oam = oam; + Sppal = sppal; + Bgpal = bgpal; + _monitor = monitor; + } + + public void Enter() + { + _monitor?.Enter(); + } + + public void Exit() + { + _monitor?.Exit(); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs index 4801cd7d45..aba870b558 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs @@ -33,5 +33,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy [BizImport(CC)] public abstract bool Init(bool cgb, byte[] spc, int spclen); + + [BizImport(CC)] + public abstract void GetGpuMemory(IntPtr[] ptrs); + + [BizImport(CC)] + public abstract void SetScanlineCallback(ScanlineCallback callback, int ly); + + [BizImport(CC)] + public abstract byte GetIoReg(byte port); } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs index 52b966647b..90fecb3b99 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { [Core("Pizza Boy", "Davide Berra", true, true, "c7bc6ee376028b3766de8d7a02e60ab794841f45", "https://github.com/davideberra/emu-pizza/", false)] - public class Pizza : WaterboxCore, IGameboyCommon + public class Pizza : WaterboxCore { private LibPizza _pizza; private readonly bool _sgb; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index a82a4e2e21..aaa1a1a006 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -88,6 +88,10 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy _exe.RemoveReadonlyFile("boot.rom"); PostInit(); + + var scratch = new IntPtr[4]; + _core.GetGpuMemory(scratch); + _gpuMemory = new GPUMemoryAreas(scratch[0], scratch[1], scratch[3], scratch[2], _exe); } #region Controller @@ -152,6 +156,53 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy }; } + protected override void FrameAdvancePost() + { + if (_scanlineCallback != null && _scanlineCallbackLine == -1) + _scanlineCallback(_core.GetIoReg(0x40)); + } + + protected override void LoadStateBinaryInternal(BinaryReader reader) + { + UpdateCoreScanlineCallback(false); + } + public bool IsCGBMode() => _cgb; + + private GPUMemoryAreas _gpuMemory; + + public GPUMemoryAreas GetGPU() => _gpuMemory; + private ScanlineCallback _scanlineCallback; + private int _scanlineCallbackLine; + + public void SetScanlineCallback(ScanlineCallback callback, int line) + { + _scanlineCallback = callback; + _scanlineCallbackLine = line; + UpdateCoreScanlineCallback(true); + } + + private void UpdateCoreScanlineCallback(bool now) + { + if (_scanlineCallback == null) + { + _core.SetScanlineCallback(null, -1); + } + else + { + if (_scanlineCallbackLine >= 0 && _scanlineCallbackLine <= 153) + { + _core.SetScanlineCallback(_scanlineCallback, _scanlineCallbackLine); + } + else + { + _core.SetScanlineCallback(null, -1); + if (_scanlineCallbackLine == -2 && now) + { + _scanlineCallback(_core.GetIoReg(0x40)); + } + } + } + } } } diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index 531e4f4eeb..15db12b9d6 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -207,6 +207,24 @@ ECL_EXPORT void SetInputCallback(void (*callback)()) GB_set_input_callback(&GB, callback ? InputCallback : nullptr); } +ECL_EXPORT void GetGpuMemory(void **p) +{ + p[0] = GB_get_direct_access(&GB, GB_DIRECT_ACCESS_VRAM, nullptr, nullptr); + p[1] = GB_get_direct_access(&GB, GB_DIRECT_ACCESS_OAM, nullptr, nullptr); + p[2] = GB.background_palettes_rgb; + p[3] = GB.sprite_palettes_rgb; +} + +ECL_EXPORT void SetScanlineCallback(void (*callback)(uint8_t), int ly) +{ + GB.scanline_callback = callback; + GB.scanline_callback_ly = ly; +} +ECL_EXPORT uint8_t GetIoReg(uint8_t port) +{ + return GB.io_registers[port]; +} + int main() { return 0; diff --git a/waterbox/sameboy/display.c b/waterbox/sameboy/display.c index d2ee911619..28dd5f07ec 100644 --- a/waterbox/sameboy/display.c +++ b/waterbox/sameboy/display.c @@ -296,7 +296,10 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) } bool should_compare_ly = true; uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH; - + + if (gb->scanline_callback && gb->display_cycles % LINE_LENGTH == 0 && gb->scanline_callback_ly == ly_for_comparison) { + gb->scanline_callback(gb->io_registers[GB_IO_LCDC]); + } /* Handle cycle completion. STAT's initial value depends on model and mode */ if (gb->display_cycles == LCDC_PERIOD) { @@ -380,7 +383,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) /* Handle STAT changes for lines 0-143 */ else if (gb->display_cycles < LINES * LINE_LENGTH) { unsigned position_in_line = gb->display_cycles % LINE_LENGTH; - + /* Handle OAM and VRAM blocking */ /* Todo: verify CGB timing for write blocking */ if (position_in_line == stat_delay - oam_blocking_rush || diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index 9b5f4613dd..91d29effe4 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -172,6 +172,7 @@ typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); typedef void (*GB_serial_transfer_start_callback_t)(GB_gameboy_t *gb, uint8_t byte_to_send); typedef uint8_t (*GB_serial_transfer_end_callback_t)(GB_gameboy_t *gb); typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock); +typedef void (*GB_scanline_callback_t)(uint8_t lcdc); typedef struct { bool state; @@ -400,7 +401,10 @@ struct GB_gameboy_internal_s { GB_serial_transfer_start_callback_t serial_transfer_start_callback; GB_serial_transfer_end_callback_t serial_transfer_end_callback; GB_sample_callback_t sample_callback; - + + GB_scanline_callback_t scanline_callback; + int scanline_callback_ly; + /* IR */ long cycles_since_ir_change; long cycles_since_input_ir_change;