Rework GB GPU memory areas API (#2566)
* Rework GB GPU memory areas API All cores can easily implement it now with no copying or awkward garbage. Also fix the scanline callback and printer callback in Sameboy, which had been broken for some time. Fixes #2564
This commit is contained in:
parent
e71506ac6a
commit
0b432994df
|
@ -32,13 +32,24 @@ namespace BizHawk.Client.EmuHawk
|
||||||
// g' = 8.25g
|
// g' = 8.25g
|
||||||
// b' = 8.25b
|
// b' = 8.25b
|
||||||
|
|
||||||
|
|
||||||
private GPUMemoryAreas _memory;
|
|
||||||
|
|
||||||
private bool _cgb; // set once at start
|
private bool _cgb; // set once at start
|
||||||
private int _lcdc; // set at each callback
|
private int _lcdc; // set at each callback
|
||||||
|
|
||||||
private IntPtr _tilesPal; // current palette to use on tiles
|
/// <summary>
|
||||||
|
/// Whether the tiles are being drawn with the sprite or bg palettes
|
||||||
|
/// </summary>
|
||||||
|
private bool _tilesPalIsSprite;
|
||||||
|
/// <summary>
|
||||||
|
/// How far (in bytes, I guess?) we should offset into the tiles palette
|
||||||
|
/// </summary>
|
||||||
|
private int _tilesPalOffset;
|
||||||
|
|
||||||
|
private IntPtr ComputeTilesPalFromMemory(IGPUMemoryAreas m)
|
||||||
|
{
|
||||||
|
var ret = _tilesPalIsSprite ? m.Sppal : m.Bgpal;
|
||||||
|
ret += _tilesPalOffset;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
private Color _spriteback;
|
private Color _spriteback;
|
||||||
|
|
||||||
|
@ -86,9 +97,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
_cgb = Gb.IsCGBMode();
|
_cgb = Gb.IsCGBMode();
|
||||||
_lcdc = 0;
|
_lcdc = 0;
|
||||||
_memory = Gb.GetGPU();
|
|
||||||
|
|
||||||
_tilesPal = _memory.Bgpal;
|
|
||||||
|
|
||||||
label4.Enabled = _cgb;
|
label4.Enabled = _cgb;
|
||||||
bmpViewBG.Clear();
|
bmpViewBG.Clear();
|
||||||
|
@ -420,17 +428,19 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private void ScanlineCallback(byte lcdc)
|
private void ScanlineCallback(byte lcdc)
|
||||||
{
|
{
|
||||||
using (_memory.EnterExit())
|
using (var memory = Gb.LockGPU())
|
||||||
{
|
{
|
||||||
var bgPal = _memory.Bgpal;
|
var bgPal = memory.Bgpal;
|
||||||
var spPal = _memory.Sppal;
|
var spPal = memory.Sppal;
|
||||||
var oam = _memory.Oam;
|
var oam = memory.Oam;
|
||||||
var vram = _memory.Vram;
|
var vram = memory.Vram;
|
||||||
|
var tilesPal = ComputeTilesPalFromMemory(memory);
|
||||||
|
|
||||||
_lcdc = lcdc;
|
_lcdc = lcdc;
|
||||||
// set alpha on all pixels
|
// set alpha on all pixels
|
||||||
#if false
|
#if false
|
||||||
// TODO: RE: Spriteback, you can't muck with Sameboy in this way due to how SGB reads stuff...?
|
// TODO: This probably shouldn't be done on any cores at all. Let the tool make a separate copy of palettes if it needs alpha,
|
||||||
|
// or compel the cores to send data with alpha already set. What was this actually solving?
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
int* p = (int*)_bgpal;
|
int* p = (int*)_bgpal;
|
||||||
|
@ -484,11 +494,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
// tile display
|
// tile display
|
||||||
// TODO: user selects palette to use, instead of fixed palette 0
|
// 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?
|
// 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);
|
DrawTiles(bmpViewTiles1.Bmp, vram, tilesPal);
|
||||||
bmpViewTiles1.Refresh();
|
bmpViewTiles1.Refresh();
|
||||||
if (_cgb)
|
if (_cgb)
|
||||||
{
|
{
|
||||||
DrawTiles(bmpViewTiles2.Bmp, vram + 0x2000, _tilesPal);
|
DrawTiles(bmpViewTiles2.Bmp, vram + 0x2000, tilesPal);
|
||||||
bmpViewTiles2.Refresh();
|
bmpViewTiles2.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,10 +676,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private unsafe void PaletteMouseover(int x, int y, bool sprite)
|
private unsafe void PaletteMouseover(int x, int y, bool sprite)
|
||||||
{
|
{
|
||||||
using (_memory.EnterExit())
|
using (var memory = Gb.LockGPU())
|
||||||
{
|
{
|
||||||
var bgPal = _memory.Bgpal;
|
var bgPal = memory.Bgpal;
|
||||||
var spPal = _memory.Sppal;
|
var spPal = memory.Sppal;
|
||||||
|
|
||||||
bmpViewDetails.ChangeBitmapSize(8, 10);
|
bmpViewDetails.ChangeBitmapSize(8, 10);
|
||||||
if (bmpViewDetails.Height != 80)
|
if (bmpViewDetails.Height != 80)
|
||||||
|
@ -712,9 +722,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private unsafe void TileMouseover(int x, int y, bool secondBank)
|
private unsafe void TileMouseover(int x, int y, bool secondBank)
|
||||||
{
|
{
|
||||||
using (_memory.EnterExit())
|
using (var memory = Gb.LockGPU())
|
||||||
{
|
{
|
||||||
var vram = _memory.Vram;
|
var vram = memory.Vram;
|
||||||
|
var tilesPal = ComputeTilesPalFromMemory(memory);
|
||||||
|
|
||||||
// todo: draw with a specific palette
|
// todo: draw with a specific palette
|
||||||
bmpViewDetails.ChangeBitmapSize(8, 8);
|
bmpViewDetails.ChangeBitmapSize(8, 8);
|
||||||
|
@ -730,7 +741,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
: $"Tile #{tileIndex} @{tileOffset + 0x8000:x4}");
|
: $"Tile #{tileIndex} @{tileOffset + 0x8000:x4}");
|
||||||
|
|
||||||
var lockData = bmpViewDetails.Bmp.LockBits(new Rectangle(0, 0, 8, 8), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
var lockData = bmpViewDetails.Bmp.LockBits(new Rectangle(0, 0, 8, 8), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||||
DrawTile((byte*)vram + tileOffset + (secondBank ? 8192 : 0), (int*)lockData.Scan0, lockData.Stride / sizeof(int), (int*)_tilesPal);
|
DrawTile((byte*)vram + tileOffset + (secondBank ? 8192 : 0), (int*)lockData.Scan0, lockData.Stride / sizeof(int), (int*)tilesPal);
|
||||||
bmpViewDetails.Bmp.UnlockBits(lockData);
|
bmpViewDetails.Bmp.UnlockBits(lockData);
|
||||||
labelDetails.Text = sb.ToString();
|
labelDetails.Text = sb.ToString();
|
||||||
bmpViewDetails.Refresh();
|
bmpViewDetails.Refresh();
|
||||||
|
@ -739,10 +750,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private unsafe void TileMapMouseover(int x, int y, bool win)
|
private unsafe void TileMapMouseover(int x, int y, bool win)
|
||||||
{
|
{
|
||||||
using (_memory.EnterExit())
|
using (var memory = Gb.LockGPU())
|
||||||
{
|
{
|
||||||
var _bgpal = _memory.Bgpal;
|
var _bgpal = memory.Bgpal;
|
||||||
var _vram = _memory.Vram;
|
var _vram = memory.Vram;
|
||||||
|
|
||||||
bmpViewDetails.ChangeBitmapSize(8, 8);
|
bmpViewDetails.ChangeBitmapSize(8, 8);
|
||||||
if (bmpViewDetails.Height != 64)
|
if (bmpViewDetails.Height != 64)
|
||||||
|
@ -784,11 +795,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private unsafe void SpriteMouseover(int x, int y)
|
private unsafe void SpriteMouseover(int x, int y)
|
||||||
{
|
{
|
||||||
using (_memory.EnterExit())
|
using (var memory = Gb.LockGPU())
|
||||||
{
|
{
|
||||||
var spPal = _memory.Sppal;
|
var spPal = memory.Sppal;
|
||||||
var oam = _memory.Oam;
|
var oam = memory.Oam;
|
||||||
var vram = _memory.Vram;
|
var vram = memory.Vram;
|
||||||
|
|
||||||
bool tall = _lcdc.Bit(2);
|
bool tall = _lcdc.Bit(2);
|
||||||
x /= 8;
|
x /= 8;
|
||||||
|
@ -974,13 +985,21 @@ namespace BizHawk.Client.EmuHawk
|
||||||
private void bmpView_MouseClick(object sender, MouseEventArgs e)
|
private void bmpView_MouseClick(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Button == MouseButtons.Right)
|
if (e.Button == MouseButtons.Right)
|
||||||
|
{
|
||||||
SetFreeze();
|
SetFreeze();
|
||||||
|
}
|
||||||
else if (e.Button == MouseButtons.Left)
|
else if (e.Button == MouseButtons.Left)
|
||||||
{
|
{
|
||||||
if (sender == bmpViewBGPal)
|
if (sender == bmpViewBGPal)
|
||||||
_tilesPal = _memory.Bgpal + e.X / 16 * 16;
|
{
|
||||||
|
_tilesPalIsSprite = false;
|
||||||
|
_tilesPalOffset = e.X / 16 * 16;
|
||||||
|
}
|
||||||
else if (sender == bmpViewSPPal)
|
else if (sender == bmpViewSPPal)
|
||||||
_tilesPal = _memory.Sppal + e.X / 16 * 16;
|
{
|
||||||
|
_tilesPalIsSprite = true;
|
||||||
|
_tilesPalOffset = e.X / 16 * 16;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
namespace System.Runtime.CompilerServices
|
||||||
|
{
|
||||||
|
public static class IsExternalInit {}
|
||||||
|
}
|
|
@ -528,7 +528,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
{
|
{
|
||||||
if (Core._scanlineCallback != null)
|
if (Core._scanlineCallback != null)
|
||||||
{
|
{
|
||||||
Core.GetGPU();
|
|
||||||
Core._scanlineCallback(LCDC);
|
Core._scanlineCallback(LCDC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -529,7 +529,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
{
|
{
|
||||||
if (Core._scanlineCallback != null)
|
if (Core._scanlineCallback != null)
|
||||||
{
|
{
|
||||||
Core.GetGPU();
|
|
||||||
Core._scanlineCallback(LCDC);
|
Core._scanlineCallback(LCDC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
{
|
{
|
||||||
if (_scanlineCallbackLine == -1)
|
if (_scanlineCallbackLine == -1)
|
||||||
{
|
{
|
||||||
GetGPU();
|
LockGPU();
|
||||||
_scanlineCallback(ppu.LCDC);
|
_scanlineCallback(ppu.LCDC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,11 +425,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Marshal.FreeHGlobal(iptr0);
|
|
||||||
Marshal.FreeHGlobal(iptr1);
|
|
||||||
Marshal.FreeHGlobal(iptr2);
|
|
||||||
Marshal.FreeHGlobal(iptr3);
|
|
||||||
|
|
||||||
audio.DisposeSound();
|
audio.DisposeSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -224,11 +224,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
cpu.SetCallbacks(ReadMemory, PeekMemory, PeekMemory, WriteMemory);
|
cpu.SetCallbacks(ReadMemory, PeekMemory, PeekMemory, WriteMemory);
|
||||||
HardReset();
|
HardReset();
|
||||||
|
|
||||||
iptr0 = Marshal.AllocHGlobal(VRAM.Length + 1);
|
|
||||||
iptr1 = Marshal.AllocHGlobal(OAM.Length + 1);
|
|
||||||
iptr2 = Marshal.AllocHGlobal(ppu.color_palette.Length * 8 * 8 + 1);
|
|
||||||
iptr3 = Marshal.AllocHGlobal(ppu.color_palette.Length * 8 * 8 + 1);
|
|
||||||
|
|
||||||
_scanlineCallback = null;
|
_scanlineCallback = null;
|
||||||
|
|
||||||
DeterministicEmulation = true;
|
DeterministicEmulation = true;
|
||||||
|
@ -236,54 +231,93 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
|
|
||||||
public bool IsCGBMode() => is_GBC;
|
public bool IsCGBMode() => is_GBC;
|
||||||
|
|
||||||
public IntPtr iptr0 = IntPtr.Zero;
|
/// <summary>
|
||||||
public IntPtr iptr1 = IntPtr.Zero;
|
/// Produces a palette in the form that certain frontend inspection tools.
|
||||||
public IntPtr iptr2 = IntPtr.Zero;
|
/// May or may not return a reference to the core's own palette, so please don't mutate.
|
||||||
public IntPtr iptr3 = IntPtr.Zero;
|
/// </summary>
|
||||||
|
private uint[] SynthesizeFrontendBGPal()
|
||||||
private GPUMemoryAreas _gpuMemory
|
|
||||||
{
|
{
|
||||||
get
|
|
||||||
{
|
|
||||||
Marshal.Copy(VRAM, 0, iptr0, VRAM.Length);
|
|
||||||
Marshal.Copy(OAM, 0, iptr1, OAM.Length);
|
|
||||||
|
|
||||||
if (is_GBC)
|
if (is_GBC)
|
||||||
{
|
{
|
||||||
int[] cp2 = new int[32];
|
return ppu.BG_palette;
|
||||||
int[] cp = new int[32];
|
|
||||||
for (int i = 0; i < 32; i++)
|
|
||||||
{
|
|
||||||
cp2[i] = (int)ppu.OBJ_palette[i];
|
|
||||||
cp[i] = (int)ppu.BG_palette[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.Copy(cp2, 0, iptr2, ppu.OBJ_palette.Length);
|
|
||||||
Marshal.Copy(cp, 0, iptr3, ppu.BG_palette.Length);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int[] cp2 = new int[8];
|
var scratch = new uint[4];
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
cp2[i] = (int)ppu.color_palette[(ppu.obj_pal_0 >> (i * 2)) & 3];
|
scratch[i] = ppu.color_palette[(ppu.BGP >> (i * 2)) & 3];
|
||||||
cp2[i + 4] = (int)ppu.color_palette[(ppu.obj_pal_1 >> (i * 2)) & 3];
|
}
|
||||||
|
return scratch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Marshal.Copy(cp2, 0, iptr2, cp2.Length);
|
|
||||||
|
|
||||||
int[] cp = new int[4];
|
/// <summary>
|
||||||
|
/// Produces a palette in the form that certain frontend inspection tools.
|
||||||
|
/// May or may not return a reference to the core's own palette, so please don't mutate.
|
||||||
|
/// </summary>
|
||||||
|
private uint[] SynthesizeFrontendSPPal()
|
||||||
|
{
|
||||||
|
if (is_GBC)
|
||||||
|
{
|
||||||
|
return ppu.OBJ_palette;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var scratch = new uint[8];
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
cp[i] = (int)ppu.color_palette[(ppu.BGP >> (i * 2)) & 3];
|
scratch[i] = ppu.color_palette[(ppu.obj_pal_0 >> (i * 2)) & 3];
|
||||||
|
scratch[i + 4] = ppu.color_palette[(ppu.obj_pal_1 >> (i * 2)) & 3];
|
||||||
}
|
}
|
||||||
Marshal.Copy(cp, 0, iptr3, cp.Length);
|
return scratch;
|
||||||
}
|
|
||||||
|
|
||||||
return new GPUMemoryAreas(iptr0, iptr1, iptr2, iptr3);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GPUMemoryAreas GetGPU() => _gpuMemory;
|
public IGPUMemoryAreas LockGPU()
|
||||||
|
{
|
||||||
|
return new GPUMemoryAreas(
|
||||||
|
VRAM,
|
||||||
|
OAM,
|
||||||
|
SynthesizeFrontendSPPal(),
|
||||||
|
SynthesizeFrontendBGPal()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GPUMemoryAreas : IGPUMemoryAreas
|
||||||
|
{
|
||||||
|
public IntPtr Vram { get; }
|
||||||
|
|
||||||
|
public IntPtr Oam { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Sppal { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Bgpal { get; init; }
|
||||||
|
|
||||||
|
private readonly List<GCHandle> _handles = new();
|
||||||
|
|
||||||
|
public GPUMemoryAreas(byte[] vram, byte[] oam, uint[] sppal, uint[] bgpal)
|
||||||
|
{
|
||||||
|
Vram = AddHandle(vram);
|
||||||
|
Oam = AddHandle(oam);
|
||||||
|
Sppal = AddHandle(sppal);
|
||||||
|
Bgpal = AddHandle(bgpal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr AddHandle(object target)
|
||||||
|
{
|
||||||
|
var handle = GCHandle.Alloc(target, GCHandleType.Pinned);
|
||||||
|
_handles.Add(handle);
|
||||||
|
return handle.AddrOfPinnedObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var h in _handles)
|
||||||
|
h.Free();
|
||||||
|
_handles.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ScanlineCallback _scanlineCallback;
|
public ScanlineCallback _scanlineCallback;
|
||||||
public int _scanlineCallbackLine = 0;
|
public int _scanlineCallbackLine = 0;
|
||||||
|
@ -295,7 +329,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
|
|
||||||
if (line == -2)
|
if (line == -2)
|
||||||
{
|
{
|
||||||
GetGPU();
|
LockGPU();
|
||||||
_scanlineCallback(ppu.LCDC);
|
_scanlineCallback(ppu.LCDC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||||
{
|
{
|
||||||
if (Core._scanlineCallback != null)
|
if (Core._scanlineCallback != null)
|
||||||
{
|
{
|
||||||
Core.GetGPU();
|
|
||||||
Core._scanlineCallback(LCDC);
|
Core._scanlineCallback(LCDC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,11 +99,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||||
GambatteState = IntPtr.Zero;
|
GambatteState = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
_vram = IntPtr.Zero;
|
|
||||||
_oam = IntPtr.Zero;
|
|
||||||
_sppal = IntPtr.Zero;
|
|
||||||
_bgpal = IntPtr.Zero;
|
|
||||||
|
|
||||||
DisposeSound();
|
DisposeSound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,13 +382,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntPtr _vram = IntPtr.Zero;
|
public IGPUMemoryAreas LockGPU()
|
||||||
public IntPtr _bgpal = IntPtr.Zero;
|
|
||||||
public IntPtr _sppal = IntPtr.Zero;
|
|
||||||
public IntPtr _oam = IntPtr.Zero;
|
|
||||||
|
|
||||||
public GPUMemoryAreas GetGPU()
|
|
||||||
{
|
{
|
||||||
|
var _vram = IntPtr.Zero;
|
||||||
|
var _bgpal = IntPtr.Zero;
|
||||||
|
var _sppal = IntPtr.Zero;
|
||||||
|
var _oam = IntPtr.Zero;
|
||||||
int unused = 0;
|
int unused = 0;
|
||||||
if (!LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.vram, ref _vram, ref unused)
|
if (!LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.vram, ref _vram, ref unused)
|
||||||
|| !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.bgpal, ref _bgpal, ref unused)
|
|| !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.bgpal, ref _bgpal, ref unused)
|
||||||
|
@ -397,7 +396,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Unexpected error in gambatte_getmemoryarea");
|
throw new InvalidOperationException("Unexpected error in gambatte_getmemoryarea");
|
||||||
}
|
}
|
||||||
return new GPUMemoryAreas(_vram, _oam, _sppal, _bgpal);
|
return new GPUMemoryAreas
|
||||||
|
{
|
||||||
|
Vram = _vram,
|
||||||
|
Oam = _oam,
|
||||||
|
Sppal = _sppal,
|
||||||
|
Bgpal = _bgpal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GPUMemoryAreas : IGPUMemoryAreas
|
||||||
|
{
|
||||||
|
public IntPtr Vram { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Oam { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Sppal { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Bgpal { get; init; }
|
||||||
|
|
||||||
|
public void Dispose() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -17,7 +17,13 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
public interface IGameboyCommon : ISpecializedEmulatorService
|
public interface IGameboyCommon : ISpecializedEmulatorService
|
||||||
{
|
{
|
||||||
bool IsCGBMode();
|
bool IsCGBMode();
|
||||||
GPUMemoryAreas GetGPU();
|
|
||||||
|
/// <summary>
|
||||||
|
/// Acquire GPU memory for inspection. The returned object must be disposed as soon as the frontend
|
||||||
|
/// tool is done inspecting it, and the pointers become invalid once it is disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
IGPUMemoryAreas LockGPU();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// set up callback
|
/// set up callback
|
||||||
|
@ -32,32 +38,11 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
void SetPrinterCallback(PrinterCallback callback);
|
void SetPrinterCallback(PrinterCallback callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GPUMemoryAreas : IMonitor
|
public interface IGPUMemoryAreas : IDisposable
|
||||||
{
|
{
|
||||||
public IntPtr Vram { get; }
|
IntPtr Vram { get; }
|
||||||
public IntPtr Oam { get; }
|
IntPtr Oam { get; }
|
||||||
public IntPtr Sppal { get; }
|
IntPtr Sppal { get; }
|
||||||
public IntPtr Bgpal { get; }
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
private readonly bool _cgb;
|
private readonly bool _cgb;
|
||||||
private readonly bool _sgb;
|
private readonly bool _sgb;
|
||||||
|
|
||||||
|
private readonly IntPtr[] _cachedGpuPointers = new IntPtr[4];
|
||||||
|
|
||||||
[CoreConstructor("SGB")]
|
[CoreConstructor("SGB")]
|
||||||
public Sameboy(byte[] rom, CoreComm comm, Settings settings, SyncSettings syncSettings, bool deterministic)
|
public Sameboy(byte[] rom, CoreComm comm, Settings settings, SyncSettings syncSettings, bool deterministic)
|
||||||
: this(rom, comm, true, settings, syncSettings, deterministic)
|
: this(rom, comm, true, settings, syncSettings, deterministic)
|
||||||
|
@ -59,6 +61,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
SystemId = sgb ? "SGB" : "GB"
|
SystemId = sgb ? "SGB" : "GB"
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
_corePrinterCallback = PrinterCallbackRelay;
|
||||||
|
_coreScanlineCallback = ScanlineCallbackRelay;
|
||||||
|
|
||||||
_core = PreInit<LibSameboy>(new WaterboxOptions
|
_core = PreInit<LibSameboy>(new WaterboxOptions
|
||||||
{
|
{
|
||||||
Filename = "sameboy.wbx",
|
Filename = "sameboy.wbx",
|
||||||
|
@ -69,7 +74,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
MmapHeapSizeKB = 1024,
|
MmapHeapSizeKB = 1024,
|
||||||
SkipCoreConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck),
|
SkipCoreConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck),
|
||||||
SkipMemoryConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
|
SkipMemoryConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
|
||||||
});
|
}, new Delegate[] { _corePrinterCallback, _coreScanlineCallback });
|
||||||
|
|
||||||
_cgb = (rom[0x143] & 0xc0) == 0xc0 && !sgb;
|
_cgb = (rom[0x143] & 0xc0) == 0xc0 && !sgb;
|
||||||
_sgb = sgb;
|
_sgb = sgb;
|
||||||
|
@ -96,11 +101,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
_exe.RemoveReadonlyFile("game.rom");
|
_exe.RemoveReadonlyFile("game.rom");
|
||||||
_exe.RemoveReadonlyFile("boot.rom");
|
_exe.RemoveReadonlyFile("boot.rom");
|
||||||
|
|
||||||
PostInit();
|
_core.GetGpuMemory(_cachedGpuPointers);
|
||||||
|
|
||||||
var scratch = new IntPtr[4];
|
PostInit();
|
||||||
_core.GetGpuMemory(scratch);
|
|
||||||
_gpuMemory = new GPUMemoryAreas(scratch[0], scratch[1], scratch[3], scratch[2], _exe);
|
|
||||||
|
|
||||||
DeterministicEmulation = deterministic || !_syncSettings.UseRealTime;
|
DeterministicEmulation = deterministic || !_syncSettings.UseRealTime;
|
||||||
InitializeRtc(_syncSettings.InitialTime);
|
InitializeRtc(_syncSettings.InitialTime);
|
||||||
|
@ -273,8 +276,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
|
|
||||||
protected override unsafe void FrameAdvancePost()
|
protected override unsafe void FrameAdvancePost()
|
||||||
{
|
{
|
||||||
if (_scanlineCallback != null && _scanlineCallbackLine == -1)
|
if (_frontendScanlineCallback != null && _scanlineCallbackLine == -1)
|
||||||
_scanlineCallback(_core.GetIoReg(0x40));
|
_frontendScanlineCallback(_core.GetIoReg(0x40));
|
||||||
|
|
||||||
if (_sgb && !_settings.ShowSgbBorder)
|
if (_sgb && !_settings.ShowSgbBorder)
|
||||||
{
|
{
|
||||||
|
@ -299,27 +302,73 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
protected override void LoadStateBinaryInternal(BinaryReader reader)
|
protected override void LoadStateBinaryInternal(BinaryReader reader)
|
||||||
{
|
{
|
||||||
UpdateCoreScanlineCallback(false);
|
UpdateCoreScanlineCallback(false);
|
||||||
_core.SetPrinterCallback(_printerCallback);
|
_core.SetPrinterCallback(_corePrinterCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsCGBMode() => _cgb;
|
public bool IsCGBMode() => _cgb;
|
||||||
|
|
||||||
private readonly GPUMemoryAreas _gpuMemory;
|
public IGPUMemoryAreas LockGPU()
|
||||||
|
{
|
||||||
|
_exe.Enter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new GPUMemoryAreas(_exe)
|
||||||
|
{
|
||||||
|
Vram = _cachedGpuPointers[0],
|
||||||
|
Oam = _cachedGpuPointers[1],
|
||||||
|
Sppal = _cachedGpuPointers[3],
|
||||||
|
Bgpal = _cachedGpuPointers[2]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_exe.Exit();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public GPUMemoryAreas GetGPU() => _gpuMemory;
|
private class GPUMemoryAreas : IGPUMemoryAreas
|
||||||
private ScanlineCallback _scanlineCallback;
|
{
|
||||||
|
private IMonitor _monitor;
|
||||||
|
public IntPtr Vram { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Oam { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Sppal { get; init; }
|
||||||
|
|
||||||
|
public IntPtr Bgpal { get; init; }
|
||||||
|
|
||||||
|
public GPUMemoryAreas(IMonitor monitor)
|
||||||
|
{
|
||||||
|
_monitor = monitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_monitor?.Exit();
|
||||||
|
_monitor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ScanlineCallback _coreScanlineCallback;
|
||||||
|
private ScanlineCallback _frontendScanlineCallback;
|
||||||
private int _scanlineCallbackLine;
|
private int _scanlineCallbackLine;
|
||||||
|
|
||||||
|
private void ScanlineCallbackRelay(byte lcdc)
|
||||||
|
{
|
||||||
|
_frontendScanlineCallback?.Invoke(lcdc);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetScanlineCallback(ScanlineCallback callback, int line)
|
public void SetScanlineCallback(ScanlineCallback callback, int line)
|
||||||
{
|
{
|
||||||
_scanlineCallback = callback;
|
_frontendScanlineCallback = callback;
|
||||||
_scanlineCallbackLine = line;
|
_scanlineCallbackLine = line;
|
||||||
UpdateCoreScanlineCallback(true);
|
UpdateCoreScanlineCallback(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCoreScanlineCallback(bool now)
|
private void UpdateCoreScanlineCallback(bool now)
|
||||||
{
|
{
|
||||||
if (_scanlineCallback == null)
|
if (_frontendScanlineCallback == null)
|
||||||
{
|
{
|
||||||
_core.SetScanlineCallback(null, -1);
|
_core.SetScanlineCallback(null, -1);
|
||||||
}
|
}
|
||||||
|
@ -327,25 +376,31 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
||||||
{
|
{
|
||||||
if (_scanlineCallbackLine >= 0 && _scanlineCallbackLine <= 153)
|
if (_scanlineCallbackLine >= 0 && _scanlineCallbackLine <= 153)
|
||||||
{
|
{
|
||||||
_core.SetScanlineCallback(_scanlineCallback, _scanlineCallbackLine);
|
_core.SetScanlineCallback(_coreScanlineCallback, _scanlineCallbackLine);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_core.SetScanlineCallback(null, -1);
|
_core.SetScanlineCallback(null, -1);
|
||||||
if (_scanlineCallbackLine == -2 && now)
|
if (_scanlineCallbackLine == -2 && now)
|
||||||
{
|
{
|
||||||
_scanlineCallback(_core.GetIoReg(0x40));
|
_frontendScanlineCallback(_core.GetIoReg(0x40));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PrinterCallback _printerCallback;
|
private readonly PrinterCallback _corePrinterCallback;
|
||||||
|
private PrinterCallback _frontendPrinterCallback;
|
||||||
|
|
||||||
|
private void PrinterCallbackRelay(IntPtr image, byte height, byte top_margin, byte bottom_margin, byte exposure)
|
||||||
|
{
|
||||||
|
_frontendPrinterCallback?.Invoke(image, height, top_margin, bottom_margin, exposure);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetPrinterCallback(PrinterCallback callback)
|
public void SetPrinterCallback(PrinterCallback callback)
|
||||||
{
|
{
|
||||||
_printerCallback = callback;
|
_frontendPrinterCallback = callback;
|
||||||
_core.SetPrinterCallback(callback);
|
_core.SetPrinterCallback(callback != null ? _corePrinterCallback : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue