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;