diff --git a/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs b/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs index 623984cc5a..40e949f7ae 100644 --- a/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs +++ b/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs @@ -1,10 +1,16 @@ -using BizHawk.Emulation.Common; +using BizHawk.Common; +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Sound; using BizHawk.Emulation.Cores.Waterbox; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Consoles.SNK @@ -19,13 +25,20 @@ namespace BizHawk.Emulation.Cores.Consoles.SNK private bool _disposed = false; private readonly DualSyncSound _soundProvider; private readonly SideBySideVideo _videoProvider; + private readonly LinkInterop _leftEnd; + private readonly LinkInterop _rightEnd; + private readonly LinkCable _linkCable; [CoreConstructor("DNGP")] public DualNeoGeoPort(CoreComm comm, byte[] rom, bool deterministic) { CoreComm = comm; - _left = new NeoGeoPort(comm, rom, null, deterministic, PeRunner.CanonicalStart); - _right = new NeoGeoPort(comm, rom, null, deterministic, PeRunner.AlternateStart); + _left = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.CanonicalStart); + _right = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.AlternateStart); + _linkCable = new LinkCable(); + _leftEnd = new LinkInterop(_left, _linkCable.LeftIn, _linkCable.LeftOut); + _rightEnd = new LinkInterop(_right, _linkCable.RightIn, _linkCable.RightOut); + _serviceProvider = new BasicServiceProvider(this); _soundProvider = new DualSyncSound(_left, _right); @@ -36,13 +49,217 @@ namespace BizHawk.Emulation.Cores.Consoles.SNK public void FrameAdvance(IController controller, bool render, bool rendersound = true) { - _left.FrameAdvance(new PrefixController(controller, "P1 "), render, rendersound); - _right.FrameAdvance(new PrefixController(controller, "P2 "), render, rendersound); + var t1 = Task.Run(() => + { + _left.FrameAdvance(new PrefixController(controller, "P1 "), render, rendersound); + _leftEnd.SignalEndOfFrame(); + }); + var t2 = Task.Run(() => + { + _right.FrameAdvance(new PrefixController(controller, "P2 "), render, rendersound); + _rightEnd.SignalEndOfFrame(); + }); + var t3 = Task.Run(() => + { + _linkCable.RunFrame(); + }); + Task.WaitAll(t1, t2, t3); Frame++; _soundProvider.Fetch(); _videoProvider.Fetch(); } + #region link cable + + private class LinkCable + { + public readonly BlockingCollection LeftIn = new BlockingCollection(); + public readonly BlockingCollection LeftOut = new BlockingCollection(); + public readonly BlockingCollection RightIn = new BlockingCollection(); + public readonly BlockingCollection RightOut = new BlockingCollection(); + + private readonly Queue _leftData = new Queue(); + private readonly Queue _rightData = new Queue(); + + public void RunFrame() + { + LinkRequest l = LeftIn.Take(); + LinkRequest r = RightIn.Take(); + while (true) + { + switch (l.RequestType) + { + case LinkRequest.RequestTypes.EndOfFrame: + if (r.RequestType == LinkRequest.RequestTypes.EndOfFrame) + return; + break; + case LinkRequest.RequestTypes.Write: + _leftData.Enqueue(l.Data); + l = LeftIn.Take(); + continue; + case LinkRequest.RequestTypes.Read: + case LinkRequest.RequestTypes.Poll: + if (_rightData.Count > 0) + { + LeftOut.Add(new LinkResult + { + Data = l.RequestType == LinkRequest.RequestTypes.Read ? _rightData.Dequeue() : _rightData.Peek(), + Return = true + }); + l = LeftIn.Take(); + continue; + } + else if (r.RequestType != LinkRequest.RequestTypes.Write) + { + LeftOut.Add(new LinkResult + { + Data = l.Data, + Return = false + }); + l = LeftIn.Take(); + continue; + } + else + { + break; + } + } + switch (r.RequestType) + { + case LinkRequest.RequestTypes.Write: + _rightData.Enqueue(r.Data); + r = RightIn.Take(); + continue; + case LinkRequest.RequestTypes.Read: + case LinkRequest.RequestTypes.Poll: + if (_leftData.Count > 0) + { + RightOut.Add(new LinkResult + { + Data = r.RequestType == LinkRequest.RequestTypes.Read ? _leftData.Dequeue() : _leftData.Peek(), + Return = true + }); + r = RightIn.Take(); + continue; + } + else if (l.RequestType != LinkRequest.RequestTypes.Write) + { + RightOut.Add(new LinkResult + { + Data = r.Data, + Return = false + }); + r = RightIn.Take(); + continue; + } + else + { + break; + } + } + } + } + } + + public struct LinkRequest + { + public enum RequestTypes : byte + { + Read, + Poll, + Write, + EndOfFrame + } + public RequestTypes RequestType; + public byte Data; + } + public struct LinkResult + { + public byte Data; + public bool Return; + } + + private unsafe class LinkInterop + { + private readonly BlockingCollection _push; + private readonly BlockingCollection _pull; + private NeoGeoPort _core; + private readonly IntPtr _readcb; + private readonly IntPtr _pollcb; + private readonly IntPtr _writecb; + private readonly IImportResolver _exporter; + + public LinkInterop(NeoGeoPort core, BlockingCollection push, BlockingCollection pull) + { + _core = core; + _push = push; + _pull = pull; + _exporter = BizExvoker.GetExvoker(this); + _readcb = _exporter.SafeResolve("CommsReadCallback"); + _pollcb = _exporter.SafeResolve("CommsPollCallback"); + _writecb = _exporter.SafeResolve("CommsWriteCallback"); + ConnectPointers(); + } + + private void ConnectPointers() + { + _core._neopop.SetCommsCallbacks(_readcb, _pollcb, _writecb); + } + + [BizExport(CallingConvention.Cdecl)] + public bool CommsReadCallback(byte* buffer) + { + if (buffer == null) + return true; + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.Read, + Data = *buffer + }); + var r = _pull.Take(); + *buffer = r.Data; + return r.Return; + } + [BizExport(CallingConvention.Cdecl)] + public bool CommsPollCallback(byte* buffer) + { + if (buffer == null) + return true; + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.Poll, + Data = *buffer + }); + var r = _pull.Take(); + *buffer = r.Data; + return r.Return; + } + [BizExport(CallingConvention.Cdecl)] + public void CommsWriteCallback(byte data) + { + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.Write, + Data = data + }); + } + + public void SignalEndOfFrame() + { + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.EndOfFrame + }); + } + + public void PostLoadState() + { + ConnectPointers(); + } + } + + #endregion + private class PrefixController : IController { public PrefixController(IController controller, string prefix) diff --git a/BizHawk.Emulation.Cores/Consoles/SNK/LibNeoGeoPort.cs b/BizHawk.Emulation.Cores/Consoles/SNK/LibNeoGeoPort.cs index 93b5767d1a..08214a89f5 100644 --- a/BizHawk.Emulation.Cores/Consoles/SNK/LibNeoGeoPort.cs +++ b/BizHawk.Emulation.Cores/Consoles/SNK/LibNeoGeoPort.cs @@ -44,5 +44,7 @@ namespace BizHawk.Emulation.Cores.Consoles.SNK public abstract void SetInputCallback(InputCallback callback); [BizImport(CC)] public abstract void GetMemoryArea(int which, ref IntPtr ptr, ref int size, ref bool writable); + [BizImport(CC)] + public abstract void SetCommsCallbacks(IntPtr readcb, IntPtr pollcb, IntPtr writecb); } } diff --git a/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs b/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs index 108473d2c1..e21d97e4d3 100644 --- a/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs +++ b/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs @@ -4,28 +4,31 @@ using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Waterbox; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Consoles.SNK { - [CoreAttributes("NeoPop", "Thomas Klausner", true, false, "0.9.44.1", + [CoreAttributes("NeoPop", "Thomas Klausner", true, false, "0.9.44.1", "https://mednafen.github.io/releases/", false)] public class NeoGeoPort : IEmulator, IVideoProvider, ISoundProvider, IStatable, IInputPollable, ISettable { private PeRunner _exe; - private LibNeoGeoPort _neopop; + internal LibNeoGeoPort _neopop; private long _clockTime; private int _clockDen; [CoreConstructor("NGP")] public NeoGeoPort(CoreComm comm, byte[] rom, SyncSettings syncSettings, bool deterministic) - :this(comm, rom, syncSettings, deterministic, PeRunner.CanonicalStart) + : this(comm, rom, syncSettings, deterministic, PeRunner.CanonicalStart) { } diff --git a/BizHawk.Emulation.Cores/SideBySideVideo.cs b/BizHawk.Emulation.Cores/SideBySideVideo.cs index f43495d634..e624f69a41 100644 --- a/BizHawk.Emulation.Cores/SideBySideVideo.cs +++ b/BizHawk.Emulation.Cores/SideBySideVideo.cs @@ -13,6 +13,7 @@ namespace BizHawk.Emulation.Cores { _l = l; _r = r; + _buff = new int[BufferWidth * BufferHeight]; } private static unsafe void Blit(int* src, int srcp, int* dst, int dstp, int w, int h) @@ -31,8 +32,6 @@ namespace BizHawk.Emulation.Cores { int h = BufferHeight; int w = BufferWidth; - if (_buff.Length < w * h) - _buff = new int[w * h]; fixed(int* _pl = _l.GetVideoBuffer(), _pr = _r.GetVideoBuffer(), _pd = _buff) { @@ -42,7 +41,7 @@ namespace BizHawk.Emulation.Cores } private readonly IVideoProvider _l; private readonly IVideoProvider _r; - private int[] _buff = new int[0]; + private int[] _buff; public int BackgroundColor => _l.BackgroundColor; public int BufferHeight => _l.BufferHeight; public int BufferWidth => _l.BufferWidth * 2; diff --git a/output64/dll/ngp.wbx b/output64/dll/ngp.wbx index 1f9d87f898..ebd418af10 100644 Binary files a/output64/dll/ngp.wbx and b/output64/dll/ngp.wbx differ diff --git a/waterbox/ngp/neopop.cpp b/waterbox/ngp/neopop.cpp index 9bb4aa3735..f6b1b3186e 100644 --- a/waterbox/ngp/neopop.cpp +++ b/waterbox/ngp/neopop.cpp @@ -41,19 +41,30 @@ uint8 NGPJoyLatch; uint8 settings_language; time_t frontend_time; +int (*comms_read_cb)(uint8* buffer); +int (*comms_poll_cb)(uint8* buffer); +void (*comms_write_cb)(uint8 data); + bool system_comms_read(uint8 *buffer) { - return (0); + if (comms_read_cb) + return comms_read_cb(buffer); + else + return false; } bool system_comms_poll(uint8 *buffer) { - return (0); + if (comms_poll_cb) + return comms_poll_cb(buffer); + else + return false; } void system_comms_write(uint8 data) { - return; + if (comms_write_cb) + comms_write_cb(data); } void instruction_error(char *vaMessage, ...) @@ -195,6 +206,13 @@ EXPORT void SetInputCallback(void (*callback)()) inputcallback = callback; } +EXPORT void SetCommsCallbacks(int (*read_cb)(uint8* buffer), int (*poll_cb)(uint8* buffer), void (*write_cb)(uint8 data)) +{ + comms_read_cb = read_cb; + comms_poll_cb = poll_cb; + comms_write_cb = write_cb; +} + EXPORT void GetMemoryArea(int which, void **ptr, int *size, int *writable) { switch (which)