From ac1f9a90a11f0f5802dcb002e9d73931bbf76215 Mon Sep 17 00:00:00 2001 From: goyuken Date: Mon, 5 Nov 2012 20:15:53 +0000 Subject: [PATCH] libgambatte: switch the system bus read to use a much safer (100%?) deterministic peek. also implement core side stuff for scanline-based callback --- .../Consoles/Nintendo/Gameboy/Gambatte.cs | 70 +++++++++++------- .../Consoles/Nintendo/Gameboy/LibGambatte.cs | 21 +++++- BizHawk.MultiClient/GBtools/GBGPUView.cs | 23 +++++- .../output/dll/libgambatte.dll | Bin 177664 -> 177664 bytes libgambatte/include/gambatte.h | 1 + libgambatte/src/cinterface.cpp | 6 ++ libgambatte/src/cinterface.h | 2 + libgambatte/src/cpu.h | 7 +- libgambatte/src/gambatte.cpp | 4 + libgambatte/src/memory.cpp | 26 +++++++ libgambatte/src/memory.h | 11 +++ libgambatte/src/video.cpp | 6 +- libgambatte/src/video.h | 5 ++ 13 files changed, 148 insertions(+), 34 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs index 33cb2ed4c3..7c14e3d3a3 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -145,25 +145,10 @@ namespace BizHawk.Emulation.Consoles.GB tracecb = null; LibGambatte.gambatte_settracecallback(GambatteState, tracecb); - // todo: have the gambatte core actually call this at an appropriate time - if (scanlinecallback != null) - { - IntPtr vram = IntPtr.Zero; - IntPtr bgpal = IntPtr.Zero; - IntPtr sppal = IntPtr.Zero; - IntPtr oam = IntPtr.Zero; - int unused = 0; - 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.sppal, ref sppal, ref unused) - || !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.oam, ref oam, ref unused)) - throw new Exception(); - - scanlinecallback(vram, IsCGBMode(), LibGambatte.gambatte_cpuread(GambatteState, 0xff40), bgpal, sppal, oam); - } - LibGambatte.gambatte_runfor(GambatteState, VideoBuffer, 160, soundbuff, ref nsamp); + Console.WriteLine("==="); + // upload any modified data to the memory domains foreach (var r in MemoryRefreshers) @@ -593,18 +578,36 @@ namespace BizHawk.Emulation.Consoles.GB #endregion #region ppudebug + public bool GetGPUMemoryAreas(out IntPtr vram, out IntPtr bgpal, out IntPtr sppal, out IntPtr oam) + { + IntPtr _vram = IntPtr.Zero; + IntPtr _bgpal = IntPtr.Zero; + IntPtr _sppal = IntPtr.Zero; + IntPtr _oam = IntPtr.Zero; + int unused = 0; + 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.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; + } + vram = _vram; + bgpal = _bgpal; + sppal = _sppal; + oam = _oam; + return true; + } + /// /// /// - /// - /// - /// - /// - /// - /// - public delegate void ScanlineCallback(IntPtr vram, bool cgb, int lcdc, IntPtr bgpal, IntPtr sppal, IntPtr oam); - - ScanlineCallback scanlinecallback; + /// current value of register $ff40 (LCDC) + public delegate void ScanlineCallback(int lcdc); /// /// set up callback @@ -613,14 +616,25 @@ namespace BizHawk.Emulation.Consoles.GB /// scanline public void SetScanlineCallback(ScanlineCallback callback, int line) { + if (GambatteState == IntPtr.Zero) + // not sure how this is being reached. tried the debugger... + return; if (callback == null) - this.scanlinecallback = null; + scanlinecb = null; else if (line < 0 || line > 153) throw new ArgumentOutOfRangeException("line must be in [0, 153]"); else - this.scanlinecallback = callback; + scanlinecb = delegate() + { + callback(LibGambatte.gambatte_cpuread(GambatteState, 0xff40)); + //callback(0); + }; + + LibGambatte.gambatte_setscanlinecallback(GambatteState, scanlinecb, 0); } + LibGambatte.ScanlineCallback scanlinecb; + #endregion public void Dispose() diff --git a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/LibGambatte.cs b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/LibGambatte.cs index 17e248e7a7..3877502c0d 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/LibGambatte.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/LibGambatte.cs @@ -167,13 +167,30 @@ namespace BizHawk.Emulation.Consoles.GB [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void gambatte_settracecallback(IntPtr core, TraceCallback callback); + /// + /// type of the scanline callback + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void ScanlineCallback(); + + /// + /// set a callback to occur when ly reaches a particular scanline (so at the beginning of the scanline). + /// when the LCD is active, typically 145 will be the first callback after the beginning of frame advance, + /// and 144 will be the last callback right before frame advance returns + /// + /// opaque state pointer + /// null to clear + /// 0-153 inclusive + [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void gambatte_setscanlinecallback(IntPtr core, ScanlineCallback callback, int sl); + /// /// Sets the directory used for storing save data. The default is the same directory as the ROM Image file. /// /// opaque state pointer /// - [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern void gambatte_setsavedir(IntPtr core, string sdir); + //[DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + //public static extern void gambatte_setsavedir(IntPtr core, string sdir); /// /// Returns true if the currently loaded ROM image is treated as having CGB support. diff --git a/BizHawk.MultiClient/GBtools/GBGPUView.cs b/BizHawk.MultiClient/GBtools/GBGPUView.cs index 742d2216ec..dbb695a900 100644 --- a/BizHawk.MultiClient/GBtools/GBGPUView.cs +++ b/BizHawk.MultiClient/GBtools/GBGPUView.cs @@ -13,6 +13,15 @@ namespace BizHawk.MultiClient.GBtools { Emulation.Consoles.GB.Gameboy gb; + // gambatte doesn't modify these memory locations unless you reconstruct, so we can store + IntPtr vram; + IntPtr bgpal; + IntPtr sppal; + IntPtr oam; + + bool cgb; // set once at start + int lcdc; // set at each callback + public GBGPUView() { InitializeComponent(); @@ -30,7 +39,16 @@ namespace BizHawk.MultiClient.GBtools if (Global.Emulator is Emulation.Consoles.GB.Gameboy) { gb = Global.Emulator as Emulation.Consoles.GB.Gameboy; - if (gb.IsCGBMode()) + cgb = gb.IsCGBMode(); + lcdc = 0; + if (!gb.GetGPUMemoryAreas(out vram, out bgpal, out sppal, out oam)) + { + gb = null; + if (Visible) + Close(); + } + + if (cgb) label4.Enabled = true; else label4.Enabled = false; @@ -302,8 +320,9 @@ namespace BizHawk.MultiClient.GBtools b.UnlockBits(lockdata); } - void ScanlineCallback(IntPtr vram, bool cgb, int lcdc, IntPtr bgpal, IntPtr sppal, IntPtr oam) + void ScanlineCallback(int lcdc) { + this.lcdc = lcdc; // set alpha on all pixels unsafe { diff --git a/BizHawk.MultiClient/output/dll/libgambatte.dll b/BizHawk.MultiClient/output/dll/libgambatte.dll index 3ba2573d6560ab1b679ae52569cb84ddbcbe0497..43f431c686b5a53cde7095974de75ec4ee788714 100644 GIT binary patch delta 34400 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⁡W0 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 } +__declspec(dllexport) void gambatte_setscanlinecallback(void *core, void (*callback)(), int sl) +{ + GB *g = (GB *) core; + g->setScanlineCallback(callback, sl); +} + __declspec(dllexport) void gambatte_setsavedir(void *core, const char *sdir) { GB *g = (GB *) core; diff --git a/libgambatte/src/cinterface.h b/libgambatte/src/cinterface.h index 01b0c383d7..810347fc53 100644 --- a/libgambatte/src/cinterface.h +++ b/libgambatte/src/cinterface.h @@ -24,6 +24,8 @@ extern "C" __declspec(dllexport) void gambatte_settracecallback(void *core, void (*callback)(void *)); + __declspec(dllexport) void gambatte_setscanlinecallback(void *core, void (*callback)(), int sl); + __declspec(dllexport) void gambatte_setsavedir(void *core, const char *sdir); __declspec(dllexport) int gambatte_iscgb(void *core); diff --git a/libgambatte/src/cpu.h b/libgambatte/src/cpu.h index 640f518460..13711eef00 100644 --- a/libgambatte/src/cpu.h +++ b/libgambatte/src/cpu.h @@ -78,6 +78,10 @@ public: void setTraceCallback(void (*callback)(void *)) { tracecallback = callback; } + + void setScanlineCallback(void (*callback)(), int sl) { + memory.setScanlineCallback(callback, sl); + } void setSaveDir(const std::string &sdir) { memory.setSaveDir(sdir); @@ -110,7 +114,8 @@ public: void setGameGenie(const std::string &codes) { memory.setGameGenie(codes); } void setGameShark(const std::string &codes) { memory.setGameShark(codes); } - unsigned char ExternalRead(unsigned short addr) { return memory.read(addr, cycleCounter_); } + //unsigned char ExternalRead(unsigned short addr) { return memory.read(addr, cycleCounter_); } + unsigned char ExternalRead(unsigned short addr) { return memory.peek(addr); } void ExternalWrite(unsigned short addr, unsigned char val) { memory.write(addr, val, cycleCounter_); } }; diff --git a/libgambatte/src/gambatte.cpp b/libgambatte/src/gambatte.cpp index acb5a29b8c..5c295fbd98 100644 --- a/libgambatte/src/gambatte.cpp +++ b/libgambatte/src/gambatte.cpp @@ -107,6 +107,10 @@ void GB::setTraceCallback(void (*callback)(void *)) { p_->cpu.setTraceCallback(callback); } +void GB::setScanlineCallback(void (*callback)(), int sl) { + p_->cpu.setScanlineCallback(callback, sl); +} + void GB::setSaveDir(const std::string &sdir) { p_->cpu.setSaveDir(sdir); } diff --git a/libgambatte/src/memory.cpp b/libgambatte/src/memory.cpp index 2405711180..4aaa934cf7 100644 --- a/libgambatte/src/memory.cpp +++ b/libgambatte/src/memory.cpp @@ -570,6 +570,32 @@ unsigned Memory::nontrivial_read(const unsigned P, const unsigned long cycleCoun return ioamhram[P - 0xFE00]; } +unsigned Memory::nontrivial_peek(const unsigned P) { + if (P < 0xC000) { + if (P < 0x8000) + return cart.romdata(P >> 14)[P]; + + if (P < 0xA000) { + return cart.vrambankptr()[P]; + } + + if (cart.rsrambankptr()) + return cart.rsrambankptr()[P]; + + return cart.rtcRead(); // verified side-effect free + } + if (P < 0xFE00) + return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; + if (P >= 0xFF00 && P < 0xFF80) + return nontrivial_ff_peek(P); + return ioamhram[P - 0xFE00]; +} + +unsigned Memory::nontrivial_ff_peek(const unsigned P) { + // some regs may be somewhat wrong with this + return ioamhram[P - 0xFE00]; +} + void Memory::nontrivial_ff_write(const unsigned P, unsigned data, const unsigned long cycleCounter) { if (lastOamDmaUpdate != DISABLED_TIME) updateOamDma(cycleCounter); diff --git a/libgambatte/src/memory.h b/libgambatte/src/memory.h index 536a25f53f..9b01cbf8b9 100644 --- a/libgambatte/src/memory.h +++ b/libgambatte/src/memory.h @@ -66,6 +66,9 @@ class Memory { void nontrivial_ff_write(unsigned P, unsigned data, unsigned long cycleCounter); void nontrivial_write(unsigned P, unsigned data, unsigned long cycleCounter); + unsigned nontrivial_peek(unsigned P); + unsigned nontrivial_ff_peek(unsigned P); + void updateSerial(unsigned long cc); void updateTimaIrq(unsigned long cc); void updateIrqs(unsigned long cc); @@ -119,6 +122,10 @@ public: return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); } + unsigned peek(const unsigned P) { + return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_peek(P); + } + void write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { if (cart.wmem(P >> 12)) { cart.wmem(P >> 12)[P] = data; @@ -152,6 +159,10 @@ public: this->writeCallback = callback; } + void setScanlineCallback(void (*callback)(), int sl) { + display.setScanlineCallback(callback, sl); + } + void setEndtime(unsigned long cc, unsigned long inc); void setSoundBuffer(uint_least32_t *const buf) { sound.setBuffer(buf); } diff --git a/libgambatte/src/video.cpp b/libgambatte/src/video.cpp index 3aac3e4beb..d581304c57 100644 --- a/libgambatte/src/video.cpp +++ b/libgambatte/src/video.cpp @@ -71,7 +71,9 @@ LCD::LCD(const unsigned char *const oamram, const unsigned char *const vram, con eventTimes_(memEventRequester), statReg(0), m2IrqStatReg_(0), - m1IrqStatReg_(0) + m1IrqStatReg_(0), + scanlinecallback(0), + scanlinecallbacksl(0) { std::memset( bgpData, 0, sizeof bgpData); std::memset(objpData, 0, sizeof objpData); @@ -772,6 +774,8 @@ inline void LCD::event() { case LY_COUNT: ppu.doLyCountEvent(); eventTimes_.set(ppu.lyCounter().time()); + if (scanlinecallback && ppu.lyCounter().ly() == scanlinecallbacksl) + scanlinecallback(); break; } } diff --git a/libgambatte/src/video.h b/libgambatte/src/video.h index e58c910f28..b0cfca8560 100644 --- a/libgambatte/src/video.h +++ b/libgambatte/src/video.h @@ -150,6 +150,9 @@ class LCD { void doCgbBgColorChange(unsigned index, unsigned data, unsigned long cycleCounter); void doCgbSpColorChange(unsigned index, unsigned data, unsigned long cycleCounter); + void (*scanlinecallback)(); + int scanlinecallbacksl; + public: LCD(const unsigned char *oamram, const unsigned char *vram_in, VideoInterruptRequester memEventRequester); void reset(const unsigned char *oamram, const unsigned char *vram, bool cgb); @@ -253,6 +256,8 @@ public: unsigned long *bgPalette() { return ppu.bgPalette(); } unsigned long *spPalette() { return ppu.spPalette(); } + + void setScanlineCallback(void (*callback)(), int sl) { scanlinecallback = callback; scanlinecallbacksl = sl; } }; }