diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index a16e29dc7f..3df7b8c189 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -225,7 +225,9 @@ + + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs index 738e7c0a49..e8cf741c35 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs @@ -10,7 +10,7 @@ using BizHawk.Emulation.Cores.Consoles.Nintendo.N64; namespace BizHawk.Emulation.Cores.Nintendo.N64 { - public class N64 : IEmulator, IVideoProvider + public class N64 : IEmulator { public List> GetCpuFlagsAndRegisters() { @@ -64,24 +64,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 public string BoardName { get { return null; } } public CoreComm CoreComm { get; private set; } - public byte[] rom; - public GameInfo game; - public IVideoProvider VideoProvider { get { return this; } } - public int[] frameBuffer;// = new int[800 * 600]; - public int[] GetVideoBuffer() { return frameBuffer; } - public int VirtualWidth { get; set; } - public int BufferWidth { get; set; } - public int BufferHeight { get; set; } - public int BackgroundColor { get { return 0; } } + private N64VideoProvider videoProvider; + public IVideoProvider VideoProvider { get { return videoProvider; } } private DisplayType _display_type = DisplayType.NTSC; public DisplayType DisplayType { get { return _display_type; } } - public SpeexResampler resampler; - + private N64Audio audioProvider; public ISoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return resampler; } } + public ISyncSoundProvider SyncSoundProvider { get { return audioProvider.Resampler; } } public bool StartAsyncSound() { return false; } public void EndAsyncSound() { } @@ -118,18 +110,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 } }; - public int Frame { get; set; } + public int Frame { get; private set; } public int LagCount { get; set; } - public bool IsLagFrame { get; set; } + public bool IsLagFrame { get; private set; } public void ResetCounters() { Frame = 0; LagCount = 0; IsLagFrame = false; } + public void FrameAdvance(bool render, bool rendersound) { - RefreshMemoryCallbacks(); + audioProvider.RenderSound = rendersound; if (Controller["Reset"]) { @@ -142,10 +135,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 IsLagFrame = true; api.frame_advance(); + if (IsLagFrame) LagCount++; - Frame++; + Frame++; } + /// + /// Translates controller inputs from EmuHawk and + /// shoves them into mupen64plus + /// public void setControllers() { CoreComm.InputCallback.Call(); @@ -171,6 +169,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 } } + /// + /// Read all buttons from a controller and translate them + /// into a form the N64 understands + /// + /// Number of controller to translate + /// Bitlist of pressed buttons public int ReadController(int num) { int buttons = 0; @@ -193,7 +197,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 return buttons; } - public bool DeterministicEmulation { get; set; } + public bool DeterministicEmulation { get { return false; } } public byte[] ReadSaveRam() { @@ -212,12 +216,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 public bool SaveRamModified { get { return true; } set { } } - void SyncState(Serializer ser) - { - ser.BeginSection("N64"); - ser.EndSection(); - } - // these next 5 functions are all exact copy paste from gambatte. // if something's wrong here, it's probably wrong there too @@ -321,11 +319,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 // we RefreshMemoryCallbacks() after the triggers in case the trigger turns itself off at that point if (mcs.HasReads) - readcb = delegate(uint addr) { mcs.CallRead(addr); RefreshMemoryCallbacks(); }; + readcb = delegate(uint addr) { mcs.CallRead(addr); }; else readcb = null; if (mcs.HasWrites) - writecb = delegate(uint addr) { mcs.CallWrite(addr); RefreshMemoryCallbacks(); }; + writecb = delegate(uint addr) { mcs.CallWrite(addr); }; else writecb = null; @@ -394,11 +392,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 public void Dispose() { + videoProvider.Dispose(); + audioProvider.Dispose(); api.Dispose(); } - mupen64plusApi api; + // mupen64plus DLL Api + private mupen64plusApi api; + /// + /// Create mupen64plus Emulator + /// + /// Core communication object + /// Game information of game to load + /// Rom that should be loaded + /// N64SyncSettings object public N64(CoreComm comm, GameInfo game, byte[] rom, object SyncSettings) { int SaveType = 0; @@ -406,8 +414,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 SaveType = 1; CoreComm = comm; - this.rom = rom; - this.game = game; this.SyncSettings = (N64SyncSettings)SyncSettings ?? new N64SyncSettings(); @@ -439,7 +445,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 api = new mupen64plusApi(this, rom, this.SyncSettings.GetVPS(game), SaveType); api.SetM64PInputCallback(new mupen64plusApi.InputCallback(setControllers)); + audioProvider = new N64Audio(api); + videoProvider = new N64VideoProvider(api); + api.FrameFinished += videoProvider.DoVideoFrame; + api.VInterrupt += audioProvider.DoAudioFrame; + InitMemoryDomains(); + RefreshMemoryCallbacks(); } N64SyncSettings SyncSettings; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64Audio.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64Audio.cs new file mode 100644 index 0000000000..69dfe5d20b --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64Audio.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.N64 +{ + class N64Audio : IDisposable + { + /// + /// mupen64 DLL Api + /// + private mupen64plusApi api; + /// + /// Buffer for audio data + /// + private short[] audioBuffer = new short[0]; + private uint _samplingRate = 0; + /// + /// Currently active sampling rate + /// + public uint SamplingRate + { + get + { + return _samplingRate; + } + private set + { + _samplingRate = value; + Resampler.ChangeRate(_samplingRate, 44100, _samplingRate, 44100); + } + } + /// + /// Resampler for audio output + /// + public SpeexResampler Resampler { get; private set; } + public bool RenderSound { get; set; } + + /// + /// Creates a N64 Audio subsystem + /// + /// Mupen64 api which is used for fetching sound + public N64Audio(mupen64plusApi api) + { + this.api = api; + _samplingRate = api.GetSamplingRate(); + Resampler = new SpeexResampler(6, SamplingRate, 44100, + SamplingRate, 44100); + } + + /// + /// Fetches the audio buffer from mupen64plus and pushes it into the + /// Resampler for audio output + /// + public void DoAudioFrame() + { + uint m64pSamplingRate = api.GetSamplingRate(); + if (m64pSamplingRate != SamplingRate) + SamplingRate = m64pSamplingRate; + + int audioBufferSize = api.GetAudioBufferSize(); + if (audioBuffer.Length < audioBufferSize) + audioBuffer = new short[audioBufferSize]; + + if (audioBufferSize > 0) + { + api.GetAudioBuffer(audioBuffer); + if (RenderSound) + Resampler.EnqueueSamples(audioBuffer, audioBufferSize / 2); + } + } + + public void Dispose() + { + if(Resampler != null) + Resampler.Dispose(); + Resampler = null; + // Api is disposed by N64 + api = null; + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64VideoProvider.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64VideoProvider.cs new file mode 100644 index 0000000000..d49a819c06 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64VideoProvider.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.N64 +{ + class N64VideoProvider : IVideoProvider, IDisposable + { + private int[] frameBuffer; + private mupen64plusApi api; + + /// + /// Creates N64 Video system with mupen64plus backend + /// + /// mupen64plus DLL that is used + public N64VideoProvider(mupen64plusApi api) + { + this.api = api; + int width = 0; + int height = 0; + api.GetScreenDimensions(ref width, ref height); + SetBufferSize(width, height); + } + + public int[] GetVideoBuffer() + { + return frameBuffer; + } + + public int VirtualWidth { get { return BufferWidth; } } + public int BufferWidth { get; private set; } + public int BufferHeight { get; private set; } + public int BackgroundColor { get { return 0; } } + + /// + /// Fetches current frame buffer from mupen64 + /// + public void DoVideoFrame() + { + int width = 0; + int height = 0; + api.GetScreenDimensions(ref width, ref height); + if (width != BufferWidth || height != BufferHeight) + { + SetBufferSize(width, height); + } + api.Getm64pFrameBuffer(frameBuffer, ref width, ref height); + } + + /// + /// Sets a new width and height for frame buffer + /// + /// New width in pixels + /// New height in pixels + private void SetBufferSize(int width, int height) + { + BufferHeight = height; + BufferWidth = width; + frameBuffer = new int[width * height]; + } + + public void Dispose() + { + // api is disposed by N64 + api = null; + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/mupen64plusApi.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/mupen64plusApi.cs index 05eb26d55c..3efe85692c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/mupen64plusApi.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/mupen64plusApi.cs @@ -12,14 +12,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 { public class mupen64plusApi : IDisposable { + // Only left in because api needs to know the number of frames passed + // because of a bug private readonly N64 bizhawkCore; static mupen64plusApi AttachedCore = null; bool disposed = false; - uint m64pSamplingRate; - short[] m64pAudioBuffer = new short[2]; - Thread m64pEmulator; AutoResetEvent m64pFrameComplete = new AutoResetEvent(false); @@ -439,12 +438,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 public mupen64plusApi(N64 bizhawkCore, byte[] rom, VideoPluginSettings video_settings, int SaveType) { + // There can only be one core (otherwise breaks mupen64plus) if (AttachedCore != null) { AttachedCore.Dispose(); AttachedCore = null; } - this.bizhawkCore = bizhawkCore; string VidDllName; @@ -485,7 +484,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 connectFunctionPointers(); // Start up the core - m64p_error result = m64pCoreStartup(0x20001, "", "", "Core", (IntPtr foo, int level, string Message) => { }, "", IntPtr.Zero); + m64p_error result = m64pCoreStartup(0x20001, "", "", "Core", + (IntPtr foo, int level, string Message) => { }, + "", IntPtr.Zero); // Set the savetype if needed if (SaveType != 0) @@ -498,9 +499,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 // Pass the rom to the core result = m64pCoreDoCommandByteArray(m64p_command.M64CMD_ROM_OPEN, rom.Length, rom); - // Resize the video to the size in bizhawk's settings - SetVideoSize(video_settings.Width, video_settings.Height); - // Open the general video settings section in the config system IntPtr video_section = IntPtr.Zero; m64pConfigOpenSection("Video-General", ref video_section); @@ -511,6 +509,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 set_video_parameters(video_settings); + // Order of plugin loading is important, do not change! // Set up and connect the graphics plugin result = GfxPluginStartup(CoreDll, "Video", (IntPtr foo, int level, string Message) => { }); result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_GFX, GfxDll); @@ -520,23 +519,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_AUDIO, AudDll); // Set up our input plugin - result = AudPluginStartup(CoreDll, "Input", (IntPtr foo, int level, string Message) => { }); + result = AudPluginStartup(CoreDll, "Input", + (IntPtr foo, int level, string Message) => { }); result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_INPUT, InpDll); // Set up and connect the RSP plugin result = RspPluginStartup(CoreDll, "RSP", (IntPtr foo, int level, string Message) => { }); result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_RSP, RspDll); - // Set up the frame callback function - m64pFrameCallback = new FrameCallback(Getm64pFrameBuffer); - result = m64pCoreDoCommandFrameCallback(m64p_command.M64CMD_SET_FRAME_CALLBACK, 0, m64pFrameCallback); - - // Set up the VI callback function - m64pVICallback = new VICallback(VI); - result = m64pCoreDoCommandVICallback(m64p_command.M64CMD_SET_VI_CALLBACK, 0, m64pVICallback); - InitSaveram(); + // Initialize event invoker + m64pFrameCallback = new FrameCallback(FireFrameFinishedEvent); + result = m64pCoreDoCommandFrameCallback(m64p_command.M64CMD_SET_FRAME_CALLBACK, 0, m64pFrameCallback); + m64pVICallback = new VICallback(FireVIEvent); + result = m64pCoreDoCommandVICallback(m64p_command.M64CMD_SET_VI_CALLBACK, 0, m64pVICallback); + // Start the emulator in another thread m64pEmulator = new Thread(ExecuteEmulator); m64pEmulator.Start(); @@ -544,14 +542,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 // Wait for the core to boot up m64pStartupComplete.WaitOne(); - // Set up the resampler - m64pSamplingRate = (uint)AudGetAudioRate(); - bizhawkCore.resampler = new SpeexResampler(6, m64pSamplingRate, 44100, m64pSamplingRate, 44100, null, null); - AttachedCore = this; } volatile bool emulator_running = false; + /// + /// Starts execution of mupen64plus + /// Does not return until the emulator stops + /// public void ExecuteEmulator() { emulator_running = true; @@ -611,16 +609,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 RspPluginShutdown = (PluginShutdown)Marshal.GetDelegateForFunctionPointer(GetProcAddress(RspDll, "PluginShutdown"), typeof(PluginShutdown)); } - public void SetVideoSize(int vidX, int vidY) - { - bizhawkCore.VirtualWidth = vidX; - bizhawkCore.BufferWidth = vidX; - bizhawkCore.BufferHeight = vidY; - - bizhawkCore.frameBuffer = new int[vidX * vidY]; - m64p_FrameBuffer = new int[vidX * vidY]; - } - + /// + /// Puts plugin settings of EmuHawk into mupen64plus + /// + /// Settings to put into mupen64plus public void set_video_parameters(VideoPluginSettings video_settings) { IntPtr video_plugin_section = IntPtr.Zero; @@ -656,45 +648,34 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 } } - - int[] m64p_FrameBuffer; - + private int[] m64pBuffer = new int[0]; /// - /// This function will be used as the frame callback. It pulls the framebuffer from mupen64plus + /// This function copies the frame buffer from mupen64plus /// - public void Getm64pFrameBuffer() + public void Getm64pFrameBuffer(int[] buffer, ref int width, ref int height) { - int width = 0; - int height = 0; - - // Get the size of the frame buffer - GFXReadScreen2Res(IntPtr.Zero, ref width, ref height, 0); - - // If it's not the same size as the current one, change the sizes - if (width != bizhawkCore.BufferWidth || height != bizhawkCore.BufferHeight) - { - SetVideoSize(width, height); - } - + if(m64pBuffer.Length != width * height) + m64pBuffer = new int[width * height]; // Actually get the frame buffer - GFXReadScreen2(m64p_FrameBuffer, ref width, ref height, 0); + GFXReadScreen2(m64pBuffer, ref width, ref height, 0); // vflip - int fromindex = bizhawkCore.BufferWidth * (bizhawkCore.BufferHeight - 1) * 4; + int fromindex = width * (height - 1) * 4; int toindex = 0; - for (int j = 0; j < bizhawkCore.BufferHeight; j++) + + for (int j = 0; j < height; j++) { - Buffer.BlockCopy(m64p_FrameBuffer, fromindex, bizhawkCore.frameBuffer, toindex, bizhawkCore.BufferWidth * 4); - fromindex -= bizhawkCore.BufferWidth * 4; - toindex += bizhawkCore.BufferWidth * 4; + Buffer.BlockCopy(m64pBuffer, fromindex, buffer, toindex, width * 4); + fromindex -= width * 4; + toindex += width * 4; } // opaque unsafe { - fixed (int* ptr = &bizhawkCore.frameBuffer[0]) + fixed (int* ptr = &buffer[0]) { - int l = bizhawkCore.frameBuffer.Length; + int l = buffer.Length; for (int i = 0; i < l; i++) { ptr[i] |= unchecked((int)0xff000000); @@ -703,32 +684,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 } } - /// - /// This function will be used as the VI callback. It checks the audio rate and updates the resampler if necessary. - /// It then polls the audio buffer for samples. It then marks the frame as complete. - /// - public void VI() - { - uint s = (uint)AudGetAudioRate(); - if (s != m64pSamplingRate) - { - m64pSamplingRate = s; - bizhawkCore.resampler.ChangeRate(s, 44100, s, 44100); - //Console.WriteLine("N64 ARate Change {0}", s); - } - - int m64pAudioBufferSize = AudGetBufferSize(); - if (m64pAudioBuffer.Length < m64pAudioBufferSize) - m64pAudioBuffer = new short[m64pAudioBufferSize]; - - if (m64pAudioBufferSize > 0) - { - AudReadAudioBuffer(m64pAudioBuffer); - bizhawkCore.resampler.EnqueueSamples(m64pAudioBuffer, m64pAudioBufferSize / 2); - } - m64pFrameComplete.Set(); - } - public int get_memory_size(N64_MEMORY id) { return m64pMemGetSize(id); @@ -777,6 +732,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 // When using the dynamic recompiler if a state is loaded too early some pointers are not set up yet, so mupen // tries to access null pointers and the emulator crashes. It seems like it takes 2 frames to fully set up the recompiler, // so if two frames haven't been run yet, run them, then load the state. + if (bizhawkCore.Frame < 2) { frame_advance(); @@ -851,9 +807,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 // Backup the saveram in case bizhawk wants to get at is after we've freed the libraries saveram_backup = SaveSaveram(); - bizhawkCore.resampler.Dispose(); - bizhawkCore.resampler = null; - m64pCoreDetachPlugin(m64p_plugin_type.M64PLUGIN_GFX); m64pCoreDetachPlugin(m64p_plugin_type.M64PLUGIN_AUDIO); m64pCoreDetachPlugin(m64p_plugin_type.M64PLUGIN_INPUT); @@ -878,6 +831,49 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 disposed = true; } } + + public void GetScreenDimensions(ref int width, ref int height) + { + GFXReadScreen2Res(IntPtr.Zero, ref width, ref height, 0); + } + + public uint GetSamplingRate() + { + return (uint)AudGetAudioRate(); + } + + public int GetAudioBufferSize() + { + return AudGetBufferSize(); + } + + public void GetAudioBuffer(short[] buffer) + { + AudReadAudioBuffer(buffer); + } + + public event Action FrameFinished; + public event Action VInterrupt; + + private void FireFrameFinishedEvent() + { + // Execute Frame Callback functions + if (FrameFinished != null) + FrameFinished(); + } + + private void FireVIEvent() + { + // Execute VI Callback functions + if (VInterrupt != null) + VInterrupt(); + m64pFrameComplete.Set(); + } + + private void CompletedFrameCallback() + { + m64pFrameComplete.Set(); + } } public class VideoPluginSettings diff --git a/libmupen64plus/mupen64plus-core/projects/msvc11/mupen64plus-core.vcxproj b/libmupen64plus/mupen64plus-core/projects/msvc11/mupen64plus-core.vcxproj index 232438811d..1af28c1434 100644 --- a/libmupen64plus/mupen64plus-core/projects/msvc11/mupen64plus-core.vcxproj +++ b/libmupen64plus/mupen64plus-core/projects/msvc11/mupen64plus-core.vcxproj @@ -106,7 +106,7 @@ MachineX86 - xcopy /y $(OutDir)mupen64plus.dll $(TargetDir)..\..\..\..\..\output\dll\ + xcopy /y "$(OutDir)mupen64plus.dll" "$(TargetDir)..\..\..\..\..\output\dll\"