diff --git a/output/dll/faust.wbx b/output/dll/faust.wbx index 282e3a02d3..0251dc18a6 100644 Binary files a/output/dll/faust.wbx and b/output/dll/faust.wbx differ diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Faust/Faust.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Faust/Faust.cs index e75e78d80a..1ac9e1af15 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Faust/Faust.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Faust/Faust.cs @@ -12,12 +12,16 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Faust NymaSettings settings, NymaSyncSettings syncSettings, bool deterministic) : base(comm, "SNES", "I don't think anything uses this parameter", settings, syncSettings) { + if (deterministic) + // force ST renderer + SettingsOverrides.Add("snes_faust.renderer", "0"); + DoInit(game, rom, null, "faust.wbx", extension, deterministic); } protected override IDictionary SettingsOverrides { get; } = new Dictionary { - { "snes_faust.renderer", null }, + // { "snes_faust.renderer", null }, { "snes_faust.affinity.ppu", null }, { "snes_faust.affinity.msu1.audio", null }, { "snes_faust.affinity.msu1.data", null }, diff --git a/src/BizHawk.Emulation.Cores/Waterbox/LibNymaCore.cs b/src/BizHawk.Emulation.Cores/Waterbox/LibNymaCore.cs index 518f697467..1d6774aedb 100644 --- a/src/BizHawk.Emulation.Cores/Waterbox/LibNymaCore.cs +++ b/src/BizHawk.Emulation.Cores/Waterbox/LibNymaCore.cs @@ -172,5 +172,7 @@ namespace BizHawk.Emulation.Cores.Waterbox public delegate void CDSectorCallback(int disk, int lba, IntPtr dest); [BizImport(CC)] public abstract void SetCDCallbacks(CDTOCCallback toccallback, CDSectorCallback sectorcallback); + [BizImport(CC)] + public abstract IntPtr GetFrameThreadProc(); } } diff --git a/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs b/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs index 557af479f5..d3a1d5cb19 100644 --- a/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs +++ b/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using BizHawk.BizInvoke; using BizHawk.Common; using BizHawk.Emulation.Common; using BizHawk.Emulation.DiscSystem; @@ -135,8 +138,17 @@ namespace BizHawk.Emulation.Cores.Waterbox { Console.Error.WriteLine($"Couldn't parse DateTime \"{SettingsQuery("nyma.rtcinitialtime")}\""); } - DeterministicEmulation = deterministic || SettingsQuery("nyma.rtcrealtime") == "0"; + // Don't optimistically set deterministic, as some cores like faust can change this + DeterministicEmulation = deterministic; // || SettingsQuery("nyma.rtcrealtime") == "0"; InitializeRtc(RtcStart); + _frameThreadPtr = _nyma.GetFrameThreadProc(); + if (_frameThreadPtr != IntPtr.Zero) + { + if (deterministic) + throw new InvalidOperationException("Internal error: Core set a frame thread proc in deterministic mode"); + Console.WriteLine($"Setting up waterbox thread for {_frameThreadPtr}"); + _frameThreadStart = CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(_frameThreadPtr); + } } return t; @@ -153,11 +165,15 @@ namespace BizHawk.Emulation.Cores.Waterbox _nyma.SetFrontendSettingQuery(_settingsQueryDelegate); if (_disks != null) _nyma.SetCDCallbacks(_cdTocCallback, _cdSectorCallback); + if (_frameThreadPtr != _nyma.GetFrameThreadProc()) + throw new InvalidOperationException("_frameThreadPtr mismatch"); } // todo: bleh private GCHandle _frameAdvanceInputLock; + private volatile bool _frameThreadProcActive; + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound) { DriveLightOn = false; @@ -175,10 +191,28 @@ namespace BizHawk.Emulation.Cores.Waterbox InputPortData = (byte*)_frameAdvanceInputLock.AddrOfPinnedObject(), FrontendTime = GetRtcTime(SettingsQuery("nyma.rtcrealtime") != "0"), }; + if (_frameThreadStart != null) + { + _frameThreadProcActive = true; + Task.Run(() => + { + _frameThreadStart(); + _frameThreadProcActive = false; + }); + } return ret; } protected override void FrameAdvancePost() { + while (_frameThreadProcActive) + { + // The nyma core unmanaged code should always release the threadproc to completion + // before returning from Emulate, but even when it does occasionally the threadproc + // might not actually finish first + + // It MUST be allowed to finish now, because the theadproc doesn't know about or participate + // in the waterbox core lockout (IMonitor) directly -- it assumes the parent has handled that + } _frameAdvanceInputLock.Free(); } @@ -224,5 +258,8 @@ namespace BizHawk.Emulation.Cores.Waterbox var settingsBuff = _exe.RemoveTransientFile("inputs"); return NymaTypes.NPorts.GetRootAsNPorts(new ByteBuffer(settingsBuff)).UnPack().Values; } + + private IntPtr _frameThreadPtr; + private Action _frameThreadStart; } } diff --git a/src/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs b/src/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs index fe5c038e1b..781e9312d5 100644 --- a/src/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs +++ b/src/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs @@ -259,33 +259,39 @@ namespace BizHawk.Emulation.Cores.Waterbox public void LoadStateBinary(BinaryReader reader) { - _exe.LoadStateBinary(reader); - // other variables - Frame = reader.ReadInt32(); - LagCount = reader.ReadInt32(); - IsLagFrame = reader.ReadBoolean(); - BufferWidth = reader.ReadInt32(); - BufferHeight = reader.ReadInt32(); - _clockTime = reader.ReadInt64(); - _clockRemainder = reader.ReadInt32(); - // reset pointers here! - _core.SetInputCallback(null); - //_exe.PrintDebuggingInfo(); - LoadStateBinaryInternal(reader); + using (_exe.EnterExit()) + { + _exe.LoadStateBinary(reader); + // other variables + Frame = reader.ReadInt32(); + LagCount = reader.ReadInt32(); + IsLagFrame = reader.ReadBoolean(); + BufferWidth = reader.ReadInt32(); + BufferHeight = reader.ReadInt32(); + _clockTime = reader.ReadInt64(); + _clockRemainder = reader.ReadInt32(); + // reset pointers here! + _core.SetInputCallback(null); + //_exe.PrintDebuggingInfo(); + LoadStateBinaryInternal(reader); + } } public void SaveStateBinary(BinaryWriter writer) { - _exe.SaveStateBinary(writer); - // other variables - writer.Write(Frame); - writer.Write(LagCount); - writer.Write(IsLagFrame); - writer.Write(BufferWidth); - writer.Write(BufferHeight); - writer.Write(_clockTime); - writer.Write(_clockRemainder); - SaveStateBinaryInternal(writer); + using (_exe.EnterExit()) + { + _exe.SaveStateBinary(writer); + // other variables + writer.Write(Frame); + writer.Write(LagCount); + writer.Write(IsLagFrame); + writer.Write(BufferWidth); + writer.Write(BufferHeight); + writer.Write(_clockTime); + writer.Write(_clockRemainder); + SaveStateBinaryInternal(writer); + } } public byte[] SaveStateBinary() diff --git a/waterbox/nyma/NymaCore.cpp b/waterbox/nyma/NymaCore.cpp index 033835f633..f4b271ab67 100644 --- a/waterbox/nyma/NymaCore.cpp +++ b/waterbox/nyma/NymaCore.cpp @@ -352,3 +352,15 @@ ECL_EXPORT void DumpSettings() f.write(fbb.GetBufferPointer(), fbb.GetSize()); } } + +static FrameCallback FrameThreadProc = nullptr; + +void RegisterFrameThreadProc(FrameCallback threadproc) +{ + FrameThreadProc = threadproc; +} + +ECL_EXPORT FrameCallback GetFrameThreadProc() +{ + return FrameThreadProc; +} diff --git a/waterbox/nyma/common/nyma.h b/waterbox/nyma/common/nyma.h index 66766ac166..89d0c08ddc 100644 --- a/waterbox/nyma/common/nyma.h +++ b/waterbox/nyma/common/nyma.h @@ -17,3 +17,11 @@ CheatArea* FindCheatArea(uint32_t address); extern bool LagFlag; extern void (*InputCallback)(); extern int64_t FrontendTime; + +typedef void (*FrameCallback)(); + +// Register a callback to run each frame asynchronously +// Only one callback may be registered +// The callback may not call any C standard library functions, or otherwise trigger a syscall +// The callback must return before frame advance finishes +void RegisterFrameThreadProc(FrameCallback threadproc); diff --git a/waterbox/nyma/faust.cpp b/waterbox/nyma/faust.cpp index 33416d6129..1a76232b30 100644 --- a/waterbox/nyma/faust.cpp +++ b/waterbox/nyma/faust.cpp @@ -91,11 +91,16 @@ MemoryDomainFunctions(OAMHI, PPU_ST::PPU_PeekOAMHI, PPU_ST::PPU_PokeOAMHI); MemoryDomainFunctions(APU, APU_PeekRAM, APU_PokeRAM); -namespace MDFN_IEN_SNES_FAUST::SA1CPU +namespace MDFN_IEN_SNES_FAUST { - extern CPU_Misc CPUM; + namespace SA1CPU + { + extern CPU_Misc CPUM; + } + extern unsigned ppu_renderer; } + ECL_EXPORT void GetMemoryAreas(MemoryArea* m) { int i = 0; @@ -129,29 +134,32 @@ ECL_EXPORT void GetMemoryAreas(MemoryArea* m) i++; } - m[i].Data = (void*)(MemoryFunctionHook)AccessVRAM; - m[i].Name = "VRAM"; - m[i].Size = 64 * 1024; - m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE2 | MEMORYAREA_FLAGS_FUNCTIONHOOK; - i++; + if (ppu_renderer == PPU_RENDERER_ST) + { + m[i].Data = (void*)(MemoryFunctionHook)AccessVRAM; + m[i].Name = "VRAM"; + m[i].Size = 64 * 1024; + m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE2 | MEMORYAREA_FLAGS_FUNCTIONHOOK; + i++; - m[i].Data = (void*)(MemoryFunctionHook)AccessCGRAM; - m[i].Name = "CGRAM"; - m[i].Size = 512; - m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE2 | MEMORYAREA_FLAGS_FUNCTIONHOOK; - i++; + m[i].Data = (void*)(MemoryFunctionHook)AccessCGRAM; + m[i].Name = "CGRAM"; + m[i].Size = 512; + m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE2 | MEMORYAREA_FLAGS_FUNCTIONHOOK; + i++; - m[i].Data = (void*)(MemoryFunctionHook)AccessOAMLO; - m[i].Name = "OAMLO"; - m[i].Size = 512; - m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE2 | MEMORYAREA_FLAGS_FUNCTIONHOOK; - i++; + m[i].Data = (void*)(MemoryFunctionHook)AccessOAMLO; + m[i].Name = "OAMLO"; + m[i].Size = 512; + m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE2 | MEMORYAREA_FLAGS_FUNCTIONHOOK; + i++; - m[i].Data = (void*)(MemoryFunctionHook)AccessOAMHI; - m[i].Name = "OAMHI"; - m[i].Size = 32; - m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_FUNCTIONHOOK; - i++; + m[i].Data = (void*)(MemoryFunctionHook)AccessOAMHI; + m[i].Name = "OAMHI"; + m[i].Size = 32; + m[i].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_FUNCTIONHOOK; + i++; + } m[i].Data = (void*)(MemoryFunctionHook)AccessAPU; m[i].Name = "APURAM"; @@ -171,32 +179,32 @@ ECL_EXPORT void GetMemoryAreas(MemoryArea* m) // TODO: "System Bus" } -// stub ppu_mt since we can't support it namespace MDFN_IEN_SNES_FAUST { static MDFN_COLD uint32 DummyEventHandler(uint32 timestamp) { return SNES_EVENT_MAXTS; } +// uncomment to stub ppu mt, instead of including our hacked up version. namespace PPU_MT { -void PPU_Init(const bool IsPAL, const bool IsPALPPUBit, const bool WantFrameBeginVBlank, const uint64 affinity){} -void PPU_SetGetVideoParams(MDFNGI* gi, const unsigned caspect, const unsigned hfilter, const unsigned sls, const unsigned sle){} -snes_event_handler PPU_GetEventHandler(void){ return DummyEventHandler; } -snes_event_handler PPU_GetLineIRQEventHandler(void){ return DummyEventHandler; } -void PPU_Kill(void){} -void PPU_StartFrame(EmulateSpecStruct* espec){} -void PPU_SyncMT(void){} -void PPU_Reset(bool powering_up){} -void PPU_ResetTS(void){} -void PPU_StateAction(StateMem* sm, const unsigned load, const bool data_only){} -uint16 PPU_PeekVRAM(uint32 addr){ return 0; } -uint16 PPU_PeekCGRAM(uint32 addr){ return 0; } -uint8 PPU_PeekOAM(uint32 addr){ return 0; } -uint8 PPU_PeekOAMHI(uint32 addr){ return 0; } -uint32 PPU_GetRegister(const unsigned id, char* const special, const uint32 special_len){ return 0; } +// void PPU_Init(const bool IsPAL, const bool IsPALPPUBit, const bool WantFrameBeginVBlank, const uint64 affinity){} +// void PPU_SetGetVideoParams(MDFNGI* gi, const unsigned caspect, const unsigned hfilter, const unsigned sls, const unsigned sle){} +// snes_event_handler PPU_GetEventHandler(void){ return DummyEventHandler; } +// snes_event_handler PPU_GetLineIRQEventHandler(void){ return DummyEventHandler; } +// void PPU_Kill(void){} +// void PPU_StartFrame(EmulateSpecStruct* espec){} +// void PPU_SyncMT(void){} +// void PPU_Reset(bool powering_up){} +// void PPU_ResetTS(void){} +// void PPU_StateAction(StateMem* sm, const unsigned load, const bool data_only){} +// uint16 PPU_PeekVRAM(uint32 addr){ return 0; } +// uint16 PPU_PeekCGRAM(uint32 addr){ return 0; } +// uint8 PPU_PeekOAM(uint32 addr){ return 0; } +// uint8 PPU_PeekOAMHI(uint32 addr){ return 0; } +// uint32 PPU_GetRegister(const unsigned id, char* const special, const uint32 special_len){ return 0; } } -// and msu1 because it uses MT readers +// stub msu1 because it uses MT readers void MSU1_Init(GameFile* gf, double* IdealSoundRate, uint64 affinity_audio, uint64 affinity_data){} void MSU1_Kill(void){} void MSU1_Reset(bool powering_up){} diff --git a/waterbox/nyma/faust.mak b/waterbox/nyma/faust.mak index 87c8666602..e00b7517aa 100644 --- a/waterbox/nyma/faust.mak +++ b/waterbox/nyma/faust.mak @@ -1,7 +1,7 @@ include common.mak SRCS += \ - $(filter-out %ppu_mt.cpp %ppu_mtrender.cpp %msu1.cpp,$(call cppdir,snes_faust)) \ + $(filter-out %msu1.cpp,$(call cppdir,snes_faust)) \ mednafen/src/cheat_formats/snes.cpp \ mednafen/src/SNSFLoader.cpp mednafen/src/PSFLoader.cpp mednafen/src/SPCReader.cpp \ cdrom_dummy.cpp \ diff --git a/waterbox/nyma/mednafen b/waterbox/nyma/mednafen index 92d22d2919..3a6060d100 160000 --- a/waterbox/nyma/mednafen +++ b/waterbox/nyma/mednafen @@ -1 +1 @@ -Subproject commit 92d22d291998c3f51e49f9f213f520c50c1af5b3 +Subproject commit 3a6060d1006a16c913a57495971e2dfe9e6b976d