From 3a4de4a6cacc1269685038536ea0fc6aa0c737fd Mon Sep 17 00:00:00 2001 From: nattthebear Date: Thu, 1 Jun 2017 18:19:30 -0400 Subject: [PATCH] some prelim roughin for dual ngp --- ...izHawkSystemIdToCoreSystemEnumConverter.cs | 1 + BizHawk.Emulation.Common/Database/Database.cs | 2 +- .../BizHawk.Emulation.Cores.csproj | 7 +- .../Consoles/SNK/DualNeoGeoPort.cs | 109 ++++++++++++++++++ .../Consoles/SNK/NeoGeoPort.cs | 8 +- BizHawk.Emulation.Cores/SideBySideVideo.cs | 59 ++++++++++ .../Sound/DualSyncSound.cs | 96 +++++++++++++++ BizHawk.Emulation.Cores/Waterbox/PeRunner.cs | 15 ++- BizHawk.Emulation.Cores/Waterbox/Swappable.cs | 12 +- 9 files changed, 296 insertions(+), 13 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs create mode 100644 BizHawk.Emulation.Cores/SideBySideVideo.cs create mode 100644 BizHawk.Emulation.Cores/Sound/DualSyncSound.cs diff --git a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs index 17b9e26884..a4b66f55cd 100644 --- a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs +++ b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs @@ -98,6 +98,7 @@ namespace BizHawk.Client.ApiHawk case "VB": case "NGP": + case "DNGP": return 0; // like I give a shit default: diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index 5068cd22ed..8363845f12 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -338,7 +338,7 @@ namespace BizHawk.Emulation.Common case ".NGP": case ".NGC": - game.System = "NGP"; + game.System = "DNGP"; break; } diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 9897fac5b4..aa0d5e360a 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -229,7 +229,7 @@ - + @@ -1210,6 +1210,7 @@ + @@ -1290,6 +1291,8 @@ + + @@ -1395,4 +1398,4 @@ --> - + \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs b/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs new file mode 100644 index 0000000000..623984cc5a --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs @@ -0,0 +1,109 @@ +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Sound; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.SNK +{ + [CoreAttributes("Dual NeoPop", "Thomas Klausner and natt", true, false, "0.9.44.1", + "https://mednafen.github.io/releases/", false)] + public class DualNeoGeoPort : IEmulator + { + private NeoGeoPort _left; + private NeoGeoPort _right; + private readonly BasicServiceProvider _serviceProvider; + private bool _disposed = false; + private readonly DualSyncSound _soundProvider; + private readonly SideBySideVideo _videoProvider; + + [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); + + _serviceProvider = new BasicServiceProvider(this); + _soundProvider = new DualSyncSound(_left, _right); + _serviceProvider.Register(_soundProvider); + _videoProvider = new SideBySideVideo(_left, _right); + _serviceProvider.Register(_videoProvider); + } + + 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); + Frame++; + _soundProvider.Fetch(); + _videoProvider.Fetch(); + } + + private class PrefixController : IController + { + public PrefixController(IController controller, string prefix) + { + _controller = controller; + _prefix = prefix; + } + + private readonly IController _controller; + private readonly string _prefix; + + public ControllerDefinition Definition => null; + + public float GetFloat(string name) + { + return _controller.GetFloat(_prefix + name); + } + + public bool IsPressed(string button) + { + return _controller.IsPressed(_prefix + button); + } + } + + public ControllerDefinition ControllerDefinition => DualNeoGeoPortController; + + private static readonly ControllerDefinition DualNeoGeoPortController = new ControllerDefinition + { + BoolButtons = + { + "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 A", "P1 B", "P1 Option", "P1 Power", + "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 A", "P2 B", "P2 Option", "P2 Power" + }, + Name = "Dual NeoGeo Portable Controller" + }; + + public void ResetCounters() + { + Frame = 0; + } + + public int Frame { get; private set; } + + public IEmulatorServiceProvider ServiceProvider => _serviceProvider; + + public CoreComm CoreComm { get; } + + public bool DeterministicEmulation => _left.DeterministicEmulation && _right.DeterministicEmulation; + + public string SystemId => "DNGP"; + + public void Dispose() + { + if (!_disposed) + { + _left.Dispose(); + _right.Dispose(); + _left = null; + _right = null; + _disposed = true; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs b/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs index 957e547718..108473d2c1 100644 --- a/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs +++ b/BizHawk.Emulation.Cores/Consoles/SNK/NeoGeoPort.cs @@ -25,6 +25,11 @@ namespace BizHawk.Emulation.Cores.Consoles.SNK [CoreConstructor("NGP")] public NeoGeoPort(CoreComm comm, byte[] rom, SyncSettings syncSettings, bool deterministic) + :this(comm, rom, syncSettings, deterministic, PeRunner.CanonicalStart) + { + } + + internal NeoGeoPort(CoreComm comm, byte[] rom, SyncSettings syncSettings, bool deterministic, ulong startAddress) { ServiceProvider = new BasicServiceProvider(this); CoreComm = comm; @@ -37,7 +42,8 @@ namespace BizHawk.Emulation.Cores.Consoles.SNK SbrkHeapSizeKB = 256, SealedHeapSizeKB = 10 * 1024, // must be a bit larger than twice the ROM size InvisibleHeapSizeKB = 4, - PlainHeapSizeKB = 4 + PlainHeapSizeKB = 4, + StartAddress = startAddress }); _neopop = BizInvoker.GetInvoker(_exe, _exe); diff --git a/BizHawk.Emulation.Cores/SideBySideVideo.cs b/BizHawk.Emulation.Cores/SideBySideVideo.cs new file mode 100644 index 0000000000..f43495d634 --- /dev/null +++ b/BizHawk.Emulation.Cores/SideBySideVideo.cs @@ -0,0 +1,59 @@ +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores +{ + public class SideBySideVideo : IVideoProvider + { + public SideBySideVideo(IVideoProvider l, IVideoProvider r) + { + _l = l; + _r = r; + } + + private static unsafe void Blit(int* src, int srcp, int* dst, int dstp, int w, int h) + { + int* srcend = src + h * srcp; + while (src < srcend) + { + for (int j = 0; j < w; j++) + dst[j] = src[j]; + src += srcp; + dst += dstp; + } + } + + public unsafe void Fetch() + { + 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) + { + Blit(_pl, w / 2, _pd, w, w / 2, h); + Blit(_pr, w / 2, _pd + w / 2, w, w / 2, h); + } + } + private readonly IVideoProvider _l; + private readonly IVideoProvider _r; + private int[] _buff = new int[0]; + public int BackgroundColor => _l.BackgroundColor; + public int BufferHeight => _l.BufferHeight; + public int BufferWidth => _l.BufferWidth * 2; + public int VirtualHeight => _l.VirtualHeight; + public int VirtualWidth => _l.VirtualWidth * 2; + public int VsyncDenominator => _l.VsyncDenominator; + public int VsyncNumerator => _l.VsyncNumerator; + + public int[] GetVideoBuffer() + { + return _buff; + } + } +} diff --git a/BizHawk.Emulation.Cores/Sound/DualSyncSound.cs b/BizHawk.Emulation.Cores/Sound/DualSyncSound.cs new file mode 100644 index 0000000000..fd61031f22 --- /dev/null +++ b/BizHawk.Emulation.Cores/Sound/DualSyncSound.cs @@ -0,0 +1,96 @@ +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Sound +{ + /// + /// this thing more or less ASSumes that the two cores input to it both provide the same number of samples in each go + /// + public class DualSyncSound : ISoundProvider + { + private ISoundProvider _left; + private ISoundProvider _right; + private int _nsamp; + private short[] _samp = new short[0]; + + private short[] _leftOverflow = new short[32]; + private int _leftOverflowCount = 0; + private short[] _rightOverflow = new short[32]; + private int _rightOverflowCount = 0; + + + public DualSyncSound(ISoundProvider left, ISoundProvider right) + { + _left = left; + _right = right; + } + + private static short Mix(short[] buff, int idx) + { + int s = buff[idx * 2] + buff[idx * 2 + 1]; + if (s > 32767) + s = 32767; + if (s < -32768) + s = -32768; + return (short)s; + } + + public void Fetch() + { + int nsampl, nsampr; + short[] sampl, sampr; + _left.GetSamplesSync(out sampl, out nsampl); + _right.GetSamplesSync(out sampr, out nsampr); + + int n = Math.Min(nsampl + _leftOverflowCount, nsampr + _rightOverflowCount); + + if (_samp.Length < n * 2) + _samp = new short[n * 2]; + + int i, j; + for (i = 0, j = 0; i < _leftOverflowCount; i++, j++) + _samp[j * 2] = Mix(_leftOverflow, i); + for (i = 0; j < n; i++, j++) + _samp[j * 2] = Mix(sampl, i); + _leftOverflowCount = Math.Min(nsampl - i, 16); + Array.Copy(sampl, i * 2, _leftOverflow, 0, _leftOverflowCount * 2); + for (i = 0, j = 0; i < _rightOverflowCount; i++, j++) + _samp[j * 2 + 1] = Mix(_rightOverflow, i); + for (i = 0; j < n; i++, j++) + _samp[j * 2 + 1] = Mix(sampr, i); + _rightOverflowCount = Math.Min(nsampr - i, 16); + Array.Copy(sampr, i * 2, _rightOverflow, 0, _rightOverflowCount * 2); + + _nsamp = n; + } + + public bool CanProvideAsync => false; + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void DiscardSamples() + { + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException(); + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = _samp; + nsamp = _nsamp; + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + throw new InvalidOperationException(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs index a15521678a..3fc1fbf913 100644 --- a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs +++ b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs @@ -53,8 +53,12 @@ namespace BizHawk.Emulation.Cores.Waterbox /// can be 0, but mmap calls will crash. /// public uint MmapHeapSizeKB { get; set; } - } + /// + /// start address in memory + /// + public ulong StartAddress { get; set; } = PeRunner.CanonicalStart; + } public class PeRunner : Swappable, IImportResolver, IBinaryStateable { @@ -539,8 +543,12 @@ namespace BizHawk.Emulation.Cores.Waterbox } - // usual starting address for the executable - private static readonly ulong CanonicalStart = 0x0000036f00000000; + /// + /// usual starting point for the executable + /// + public const ulong CanonicalStart = 0x0000036f00000000; + + public const ulong AlternateStart = 0x0000036e00000000; /// /// the next place where we can put a module or heap @@ -642,6 +650,7 @@ namespace BizHawk.Emulation.Cores.Waterbox public PeRunner(PeRunnerOptions opt) { + _nextStart = opt.StartAddress; Initialize(_nextStart); using (this.EnterExit()) { diff --git a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs index cff108ada9..25d3d8d0e9 100644 --- a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs +++ b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs @@ -17,7 +17,7 @@ namespace BizHawk.Emulation.Cores.Waterbox /// /// start address /// - private ulong _lockkey; + private uint _lockkey; /// /// the the relevant lockinfo for this core @@ -39,17 +39,17 @@ namespace BizHawk.Emulation.Cores.Waterbox _memoryBlocks = null; } - protected void Initialize(ulong lockkey) + protected void Initialize(ulong startAddress) { + // any Swappables in the same 4G range are assumed to conflict + var lockkey = (uint)(startAddress >> 32); + _lockkey = lockkey; if (lockkey == 0) throw new NullReferenceException(); _currentLockInfo = LockInfos.GetOrAdd(_lockkey, new LockInfo { Sync = new object() }); } - // any Swappable is assumed to conflict with any other Swappable at the same base address, - // but not any other starting address. so don't put them too close together! - private class LockInfo { public object Sync; @@ -70,7 +70,7 @@ namespace BizHawk.Emulation.Cores.Waterbox } } - private static readonly ConcurrentDictionary LockInfos = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary LockInfos = new ConcurrentDictionary(); /// /// acquire lock and swap this into memory