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:
CasualPokePlayer 2023-05-02 16:52:48 -07:00
parent b517228475
commit 41d94e4262
3 changed files with 45 additions and 11 deletions

View File

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

View File

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

View File

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