Change up multithreaded rendering in melonDS
Instead of creating a new Task and destroying it every frame, just create a new thread and call the delegate when needed. Also allow for rendering a frame twice, this is actually possible anyways (albeit rarely done). This should also help against a potential deadlock due to the Task.Wait call on the UI thread (which has special semantics in WinForms) Also minor nitpicks in RCheevos code
This commit is contained in:
parent
b517228475
commit
41d94e4262
|
@ -242,7 +242,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
private LibRCheevos.rc_api_resolve_hash_request_t _apiParams;
|
private LibRCheevos.rc_api_resolve_hash_request_t _apiParams;
|
||||||
public int GameID { get; private set; }
|
public int GameID { get; private set; }
|
||||||
|
|
||||||
// eh? not sure I want this retried, giving the blocking behavior
|
// eh? not sure I want this retried, given the blocking behavior
|
||||||
public override bool ShouldRetry => false;
|
public override bool ShouldRetry => false;
|
||||||
|
|
||||||
protected override void ResponseCallback(byte[] serv_resp)
|
protected override void ResponseCallback(byte[] serv_resp)
|
||||||
|
|
|
@ -156,7 +156,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
if (_inactiveHttpRequests.TryPop(out var request))
|
while (_inactiveHttpRequests.TryPop(out var request))
|
||||||
{
|
{
|
||||||
Task.Run(request.DoRequest);
|
Task.Run(request.DoRequest);
|
||||||
_activeHttpRequests.Add(request);
|
_activeHttpRequests.Add(request);
|
||||||
|
|
|
@ -4,7 +4,7 @@ using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading;
|
||||||
|
|
||||||
using BizHawk.BizInvoke;
|
using BizHawk.BizInvoke;
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
|
@ -211,7 +211,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
||||||
if (_frameThreadPtr != IntPtr.Zero)
|
if (_frameThreadPtr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Setting up waterbox thread for 0x{(ulong)_frameThreadPtr:X16}");
|
Console.WriteLine($"Setting up waterbox thread for 0x{(ulong)_frameThreadPtr:X16}");
|
||||||
_frameThreadStart = CallingConventionAdapters.GetWaterboxUnsafeUnwrapped().GetDelegateForFunctionPointer<Action>(_frameThreadPtr);
|
_frameThread = new(FrameThreadProc) { IsBackground = true };
|
||||||
|
_frameThread.Start();
|
||||||
|
_frameThreadAction = CallingConventionAdapters.GetWaterboxUnsafeUnwrapped().GetDelegateForFunctionPointer<Action>(_frameThreadPtr);
|
||||||
_core.SetThreadStartCallback(_threadstartcb);
|
_core.SetThreadStartCallback(_threadstartcb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,24 +356,56 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IntPtr _frameThreadPtr;
|
private readonly IntPtr _frameThreadPtr;
|
||||||
private readonly Action _frameThreadStart;
|
private readonly Action _frameThreadAction;
|
||||||
private readonly LibMelonDS.ThreadStartCallback _threadstartcb;
|
private readonly LibMelonDS.ThreadStartCallback _threadstartcb;
|
||||||
|
|
||||||
private Task _frameThreadProcActive;
|
private readonly Thread _frameThread;
|
||||||
|
private readonly SemaphoreSlim _frameThreadStartEvent = new(0, 1);
|
||||||
|
private readonly SemaphoreSlim _frameThreadEndEvent = new(0, 1);
|
||||||
|
private bool _isDisposing;
|
||||||
|
private bool _renderThreadRanThisFrame;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
_isDisposing = true;
|
||||||
|
_frameThreadStartEvent.Release();
|
||||||
|
_frameThread?.Join();
|
||||||
|
_frameThreadStartEvent.Dispose();
|
||||||
|
_frameThreadEndEvent.Dispose();
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrameThreadProc()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
_frameThreadStartEvent.Wait();
|
||||||
|
if (_isDisposing) break;
|
||||||
|
_frameThreadAction();
|
||||||
|
_frameThreadEndEvent.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ThreadStartCallback()
|
private void ThreadStartCallback()
|
||||||
{
|
{
|
||||||
if (_frameThreadProcActive != null)
|
if (_renderThreadRanThisFrame)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Attempted to start render thread twice");
|
// This is technically possible due to the game able to force another frame to be rendered by touching vcount
|
||||||
|
// (ALSO MEANS VSYNC NUMBERS ARE KIND OF A LIE)
|
||||||
|
_frameThreadEndEvent.Wait();
|
||||||
}
|
}
|
||||||
_frameThreadProcActive = Task.Run(_frameThreadStart);
|
|
||||||
|
_renderThreadRanThisFrame = true;
|
||||||
|
_frameThreadStartEvent.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FrameAdvancePost()
|
protected override void FrameAdvancePost()
|
||||||
{
|
{
|
||||||
_frameThreadProcActive?.Wait();
|
if (_renderThreadRanThisFrame)
|
||||||
_frameThreadProcActive = null;
|
{
|
||||||
|
_frameThreadEndEvent.Wait();
|
||||||
|
_renderThreadRanThisFrame = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadStateBinaryInternal(BinaryReader reader)
|
protected override void LoadStateBinaryInternal(BinaryReader reader)
|
||||||
|
|
Loading…
Reference in New Issue