From 25c34d2ca6604950feb1cd3bcd0bdfe0b715111b Mon Sep 17 00:00:00 2001 From: goyuken Date: Tue, 11 Sep 2012 17:37:17 +0000 Subject: [PATCH] significant speedup in gambatte MemoryDomains by keeping shadow copy of native memory not quite sure at the moment what each of the domains actually is, though --- .../Consoles/Nintendo/Gameboy/Gambatte.cs | 120 ++++++++++++++---- BizHawk.Emulation/Interfaces/IEmulator.cs | 1 + 2 files changed, 97 insertions(+), 24 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs index 98da8619ba..d745e14c73 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -103,8 +103,17 @@ namespace BizHawk.Emulation.Consoles.GB // the controller callback will set this to false if it actually gets called during the frame IsLagFrame = true; + // download any modified data to the core + foreach (var r in MemoryRefreshers) + r.RefreshWrite(); + LibGambatte.gambatte_runfor(GambatteState, VideoBuffer, 160, soundbuff, ref nsamp); + // upload any modified data to the memory domains + + foreach (var r in MemoryRefreshers) + r.RefreshRead(); + soundbuffcontains = (int)nsamp; if (IsLagFrame) @@ -253,55 +262,118 @@ namespace BizHawk.Emulation.Consoles.GB get { return GbOutputComm; } } + #region MemoryDomains - MemoryDomain CreateMemoryDomain(LibGambatte.MemoryAreas which) + class MemoryRefresher + { + IntPtr data; + int length; + + byte[] CachedMemory; + + public MemoryRefresher(IntPtr data, int length) + { + this.data = data; + this.length = length; + CachedMemory = new byte[length]; + + writeneeded = false; + readneeded = false; + } + + bool readneeded; + bool writeneeded; + + /// + /// reads data from native core to managed buffer + /// + public void RefreshRead() + { + readneeded = true; + } + + /// + /// writes data from managed buffer back to core + /// + public void RefreshWrite() + { + if (writeneeded) + { + System.Runtime.InteropServices.Marshal.Copy(CachedMemory, 0, data, length); + writeneeded = false; + } + } + + public byte Peek(int addr) + { + if (readneeded) + { + System.Runtime.InteropServices.Marshal.Copy(data, CachedMemory, 0, length); + readneeded = false; + } + return CachedMemory[addr]; + } + public void Poke(int addr, byte val) + { + // a poke without any peek is certainly legal. we need to update read, because writeneeded = true means that + // all of this data will be downloaded before the next frame. so everything but that which was poked needs to + // be up to date. + if (readneeded) + { + System.Runtime.InteropServices.Marshal.Copy(data, CachedMemory, 0, length); + readneeded = false; + } + CachedMemory[addr] = val; + writeneeded = true; + } + } + + + void CreateMemoryDomain(LibGambatte.MemoryAreas which) { IntPtr data = IntPtr.Zero; int length = 0; + int i = (int)which; + if (!LibGambatte.gambatte_getmemoryarea(GambatteState, which, ref data, ref length)) throw new Exception("gambatte_getmemoryarea() failed!"); if (data == IntPtr.Zero || length <= 0) throw new Exception("bad return from gambatte_getmemoryarea()"); - Func peeker = delegate(int addr) - { - if (addr >= length || addr < 0) - throw new ArgumentOutOfRangeException(); - byte[] result = new byte[1]; - System.Runtime.InteropServices.Marshal.Copy(data + addr, result, 0, 1); - return result[0]; - }; + MemoryRefreshers[i] = new MemoryRefresher(data, length); - Action poker = delegate(int addr, byte val) - { - if (addr >= length || addr < 0) - throw new ArgumentOutOfRangeException(); - byte[] source = new byte[1]; - source[0] = val; - System.Runtime.InteropServices.Marshal.Copy(source, 0, data + addr, 1); - }; - return new MemoryDomain(which.ToString(), length, Endian.Little, peeker, poker); + MemoryDomains[i] = new MemoryDomain(which.ToString(), length, Endian.Little, MemoryRefreshers[i].Peek, MemoryRefreshers[i].Poke); } void InitMemoryDomains() { MemoryDomains = new MemoryDomain[4]; + MemoryRefreshers = new MemoryRefresher[4]; - MemoryDomains[0] = CreateMemoryDomain(LibGambatte.MemoryAreas.rambank); - MemoryDomains[1] = CreateMemoryDomain(LibGambatte.MemoryAreas.rom); - MemoryDomains[2] = CreateMemoryDomain(LibGambatte.MemoryAreas.vram); - MemoryDomains[3] = CreateMemoryDomain(LibGambatte.MemoryAreas.wram); + CreateMemoryDomain(LibGambatte.MemoryAreas.rambank); + CreateMemoryDomain(LibGambatte.MemoryAreas.rom); + CreateMemoryDomain(LibGambatte.MemoryAreas.vram); + CreateMemoryDomain(LibGambatte.MemoryAreas.wram); - // what is this supposed to be, exactly? - MainMemory = MemoryDomains[1]; + // fixme: other code brokenly assumes that MainMemory is MemoryDomains[0] + // (here, we'd want it to be MemoryDomains[2]) + + var tmp = MemoryDomains[2]; + MemoryDomains[2] = MemoryDomains[0]; + MemoryDomains[0] = tmp; + MainMemory = MemoryDomains[0]; } public IList MemoryDomains { get; private set; } public MemoryDomain MainMemory { get; private set; } + MemoryRefresher[] MemoryRefreshers; + + #endregion + public void Dispose() { LibGambatte.gambatte_destroy(GambatteState); diff --git a/BizHawk.Emulation/Interfaces/IEmulator.cs b/BizHawk.Emulation/Interfaces/IEmulator.cs index 2dd28feb44..d62725c3ba 100644 --- a/BizHawk.Emulation/Interfaces/IEmulator.cs +++ b/BizHawk.Emulation/Interfaces/IEmulator.cs @@ -41,6 +41,7 @@ namespace BizHawk // ----- Client Debugging API stuff ----- IList MemoryDomains { get; } + // this MUST BE the same as MemoryDomains[0], else DRAGONS MemoryDomain MainMemory { get; } }