Update GuiApi so that TestDrawingWithTwoScripts passes. This fixes the bug where if two lua scripts draw stuff on the same frame only one script's drawings are visible.

Replaces a bunch of obscure ThisIsTheLuaHack and locking/unlocking code with a simple pair of methods.
This commit is contained in:
SuuperW 2023-09-14 04:16:31 -05:00
parent 1bb5f9e82a
commit 0c8ce9356f
4 changed files with 36 additions and 76 deletions

View File

@ -100,7 +100,6 @@ namespace BizHawk.Client.Common
{ {
_luaContainer?.Dispose(); _luaContainer?.Dispose();
_luaContainer = Register(serviceProvider, logCallback, mainForm, displayManager, inputManager, movieSession, toolManager, config, emulator, game); _luaContainer = Register(serviceProvider, logCallback, mainForm, displayManager, inputManager, movieSession, toolManager, config, emulator, game);
((GuiApi) _luaContainer.Gui).EnableLuaAutolockHack = true;
return _luaContainer; return _luaContainer;
} }
} }

View File

@ -53,14 +53,19 @@ namespace BizHawk.Client.Common
private DisplaySurfaceID? _usingSurfaceID = null; private DisplaySurfaceID? _usingSurfaceID = null;
public bool EnableLuaAutolockHack = false;
public bool HasGUISurface => _GUISurface != null; public bool HasGUISurface => _GUISurface != null;
private bool _frameStarted = false;
public GuiApi(Action<string> logCallback, DisplayManagerBase displayManager) public GuiApi(Action<string> logCallback, DisplayManagerBase displayManager)
{ {
LogCallback = logCallback; LogCallback = logCallback;
_displayManager = displayManager; _displayManager = displayManager;
// These will set the drawing surfaces and display them.
// This allows drawing and seeing results without any actual frames occurring.
BeginFrame();
EndFrame();
} }
private SolidBrush GetBrush(Color color) private SolidBrush GetBrush(Color color)
@ -85,68 +90,19 @@ namespace BizHawk.Client.Common
private IDisplaySurface GetRelevantSurface(DisplaySurfaceID? surfaceID) private IDisplaySurface GetRelevantSurface(DisplaySurfaceID? surfaceID)
{ {
var nnID = surfaceID ?? _usingSurfaceID ?? throw new Exception(); var nnID = surfaceID ?? _usingSurfaceID ?? throw new Exception();
void ThisIsTheLuaAutolockHack()
{
try
{
UnlockSurface(nnID);
LockSurface(nnID);
}
catch (InvalidOperationException ex)
{
LogCallback(ex.ToString());
}
}
switch (nnID) switch (nnID)
{ {
case DisplaySurfaceID.EmuCore: case DisplaySurfaceID.EmuCore:
if (_GUISurface == null && EnableLuaAutolockHack) ThisIsTheLuaAutolockHack();
return _GUISurface; return _GUISurface;
case DisplaySurfaceID.Client: case DisplaySurfaceID.Client:
if (_clientSurface == null && EnableLuaAutolockHack) ThisIsTheLuaAutolockHack();
return _clientSurface; return _clientSurface;
default: default:
throw new Exception(); throw new Exception();
} }
} }
private void LockSurface(DisplaySurfaceID surfaceID)
{
switch (surfaceID)
{
case DisplaySurfaceID.EmuCore:
if (_GUISurface != null) throw new InvalidOperationException("attempt to lock surface without unlocking previous");
_GUISurface = _displayManager.LockApiHawkSurface(surfaceID, clear: true);
break;
case DisplaySurfaceID.Client:
if (_clientSurface != null) throw new InvalidOperationException("attempt to lock surface without unlocking previous");
_clientSurface = _displayManager.LockApiHawkSurface(surfaceID, clear: true);
break;
default:
throw new ArgumentException(message: "not a valid enum member", paramName: nameof(surfaceID));
}
}
private void UnlockSurface(DisplaySurfaceID surfaceID)
{
switch (surfaceID)
{
case DisplaySurfaceID.EmuCore:
if (_GUISurface != null) _displayManager.UnlockApiHawkSurface(_GUISurface);
_GUISurface = null;
break;
case DisplaySurfaceID.Client:
if (_clientSurface != null) _displayManager.UnlockApiHawkSurface(_clientSurface);
_clientSurface = null;
break;
default:
throw new ArgumentException(message: "not a valid enum member", paramName: nameof(surfaceID));
}
}
public void WithSurface(DisplaySurfaceID surfaceID, Action drawingCallsFunc) public void WithSurface(DisplaySurfaceID surfaceID, Action drawingCallsFunc)
{ {
LockSurface(surfaceID);
_usingSurfaceID = surfaceID; _usingSurfaceID = surfaceID;
try try
{ {
@ -155,26 +111,35 @@ namespace BizHawk.Client.Common
finally finally
{ {
_usingSurfaceID = null; _usingSurfaceID = null;
UnlockSurface(surfaceID);
} }
} }
public readonly ref struct LuaAutoUnlockHack /// <summary>
/// Starts drawing on new surfaces and clears them, but does not display the new surfaces.
/// Use this with EndFrame for double-buffered display (avoid flickering during drawing operations)
/// </summary>
public void BeginFrame()
{ {
private readonly GuiApi _guiApi; // I think the "Lock" stuff is mis-named. All it actually does it track what's been "locked" and use that as the current surface. (and disallow re-locking or re-unlocking)
// Anyway, calling LockSurface will return a cleared surface we can draw on without displaying it. --SuuperW
internal LuaAutoUnlockHack(GuiApi guiApi) _GUISurface = _displayManager.LockApiHawkSurface(DisplaySurfaceID.EmuCore, true);
=> _guiApi = guiApi; _clientSurface = _displayManager.LockApiHawkSurface(DisplaySurfaceID.Client, true);
_frameStarted = true;
public void Dispose() }
/// <summary>
/// Displays the current drawing surfaces.
/// More drawing can still happen on these surfaces until BeginFrame is called.
/// </summary>
public void EndFrame()
{ {
_guiApi.UnlockSurface(DisplaySurfaceID.EmuCore); if (_frameStarted)
_guiApi.UnlockSurface(DisplaySurfaceID.Client); {
_displayManager.UnlockApiHawkSurface(_GUISurface);
_displayManager.UnlockApiHawkSurface(_clientSurface);
_frameStarted = false;
} }
} }
public LuaAutoUnlockHack ThisIsTheLuaAutoUnlockHack()
=> new(this);
public void DrawNew(string name, bool clear) public void DrawNew(string name, bool clear)
{ {
@ -638,8 +603,7 @@ namespace BizHawk.Client.Common
public void Dispose() public void Dispose()
{ {
UnlockSurface(DisplaySurfaceID.EmuCore); EndFrame();
UnlockSurface(DisplaySurfaceID.Client);
foreach (var brush in _solidBrushes.Values) brush.Dispose(); foreach (var brush in _solidBrushes.Values) brush.Dispose();
foreach (var brush in _pens.Values) brush.Dispose(); foreach (var brush in _pens.Values) brush.Dispose();
} }

View File

@ -169,8 +169,6 @@ namespace BizHawk.Client.Common
public void CallSaveStateEvent(string name) public void CallSaveStateEvent(string name)
{ {
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try try
{ {
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList()) foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList())
@ -186,7 +184,7 @@ namespace BizHawk.Client.Common
public void CallLoadStateEvent(string name) public void CallLoadStateEvent(string name)
{ {
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack(); GuiAPI.BeginFrame();
try try
{ {
@ -199,13 +197,15 @@ namespace BizHawk.Client.Common
{ {
LogToLuaConsole($"error running function attached by lua function event.onloadstate\nError message: {e.Message}"); LogToLuaConsole($"error running function attached by lua function event.onloadstate\nError message: {e.Message}");
} }
GuiAPI.EndFrame();
} }
public void CallFrameBeforeEvent() public void CallFrameBeforeEvent()
{ {
if (IsUpdateSupressed) return; if (IsUpdateSupressed) return;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack(); GuiAPI.BeginFrame();
try try
{ {
@ -224,8 +224,6 @@ namespace BizHawk.Client.Common
{ {
if (IsUpdateSupressed) return; if (IsUpdateSupressed) return;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try try
{ {
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList()) foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList())
@ -237,12 +235,12 @@ namespace BizHawk.Client.Common
{ {
LogToLuaConsole($"error running function attached by lua function event.onframeend\nError message: {e.Message}"); LogToLuaConsole($"error running function attached by lua function event.onframeend\nError message: {e.Message}");
} }
GuiAPI.EndFrame();
} }
public void CallExitEvent(LuaFile lf) public void CallExitEvent(LuaFile lf)
{ {
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
foreach (var exitCallback in RegisteredFunctions foreach (var exitCallback in RegisteredFunctions
.Where(l => l.Event == NamedLuaFunction.EVENT_TYPE_ENGINESTOP .Where(l => l.Event == NamedLuaFunction.EVENT_TYPE_ENGINESTOP
&& (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread))) && (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread)))
@ -312,7 +310,6 @@ namespace BizHawk.Client.Common
public (bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf) public (bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf)
{ {
_currThread = lf.Thread; _currThread = lf.Thread;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try try
{ {

View File

@ -71,7 +71,7 @@ namespace BizHawk.Tests.Client.Common.Lua
var buffer = displayManager.RenderOffscreenLua(vp); var buffer = displayManager.RenderOffscreenLua(vp);
Assert.AreEqual(buffer.GetPixel(2, 2), Color.Red.ToArgb()); Assert.AreEqual(buffer.GetPixel(2, 2), Color.Red.ToArgb());
Assert.AreEqual(buffer.GetPixel(2, 4), 0xff00ff00); Assert.AreEqual((uint)buffer.GetPixel(2, 4), 0xff00ff00);
} }
} }