faust: Support MT renderer

and who said waterbox can't thread.  well, it sort of can't.  but it sort of can.

the speedup isn't that great, but speed is now pretty close (5%?) to snes9x in the only game that matters (final fantasy 5)
This commit is contained in:
nattthebear 2020-05-31 11:52:47 -04:00
parent 3b27eb5e91
commit 6c9b42a526
10 changed files with 143 additions and 66 deletions

Binary file not shown.

View File

@ -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<LibNymaCore>(game, rom, null, "faust.wbx", extension, deterministic);
}
protected override IDictionary<string, string> SettingsOverrides { get; } = new Dictionary<string, string>
{
{ "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 },

View File

@ -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();
}
}

View File

@ -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<Action>(_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;
}
}

View File

@ -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()

View File

@ -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;
}

View File

@ -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);

View File

@ -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){}

View File

@ -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 \

@ -1 +1 @@
Subproject commit 92d22d291998c3f51e49f9f213f520c50c1af5b3
Subproject commit 3a6060d1006a16c913a57495971e2dfe9e6b976d