Fix guest shutdown when VideoOut window is closed (#184)

Propagate Silk window close to runtime teardown so audio and CPU workers stop instead of continuing after the presentation window is dismissed.
This commit is contained in:
Mike Saito
2026-07-15 00:52:01 +03:00
committed by GitHub
parent d2f3511002
commit caf859cc52
7 changed files with 191 additions and 4 deletions
+57 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Audio;
using SharpEmu.Libs.Kernel;
using SharpEmu.Logging;
using System.Buffers.Binary;
@@ -52,6 +53,8 @@ public static class VideoOutExports
private const int VblankWaitTimeoutMilliseconds = 100;
private static Thread? _vblankPumpThread;
private static int _vblankPumpStarted;
private static volatile int _vblankPumpStopRequested;
private static volatile int _presentationWindowCloseNotified;
private static readonly object _vblankEdgeGate = new();
private static ulong _vblankEdgeSequence;
@@ -80,7 +83,7 @@ public static class VideoOutExports
var intervalTicks = Math.Max(1L, (long)(Stopwatch.Frequency / VblankHz));
var nextEdge = Stopwatch.GetTimestamp() + intervalTicks;
while (true)
while (_vblankPumpStopRequested == 0)
{
WaitUntilTimestamp(nextEdge);
PumpVblanks();
@@ -97,6 +100,59 @@ public static class VideoOutExports
}
}
public static void NotifyPresentationWindowClosed()
{
if (Interlocked.Exchange(ref _presentationWindowCloseNotified, 1) != 0)
{
return;
}
Console.Error.WriteLine("[LOADER][INFO] VideoOut presentation window closed");
RequestHostShutdown("videoout-window-closed");
}
public static void NotifyHostInterrupt()
{
if (Interlocked.Exchange(ref _presentationWindowCloseNotified, 1) != 0)
{
return;
}
Console.Error.WriteLine("[LOADER][INFO] Host interrupt requested");
RequestHostShutdown("host-interrupt");
}
private static void RequestHostShutdown(string reason)
{
AudioOutExports.ShutdownAllPorts();
StopVblankPump();
HostSessionControl.RequestShutdown(reason);
ScheduleProcessExitIfGuestDoesNotStop();
}
private static void ScheduleProcessExitIfGuestDoesNotStop()
{
ThreadPool.QueueUserWorkItem(static _ =>
{
Thread.Sleep(2000);
Environment.Exit(0);
});
}
public static void StopVblankPump()
{
if (Interlocked.Exchange(ref _vblankPumpStopRequested, 1) != 0)
{
return;
}
var thread = _vblankPumpThread;
if (thread is { IsAlive: true })
{
thread.Join(TimeSpan.FromSeconds(2));
}
}
private static void WaitUntilTimestamp(long deadlineTicks)
{
var spinThresholdTicks = Stopwatch.Frequency * 2L / 1000L;
@@ -1154,7 +1154,13 @@ internal static unsafe class VulkanVideoPresenter
_window = Window.Create(options);
_window.Load += Initialize;
_window.Render += Render;
_window.Closing += DisposeVulkan;
_window.Closing += OnWindowClosing;
}
private void OnWindowClosing()
{
VideoOutExports.NotifyPresentationWindowClosed();
DisposeVulkan();
}
public void Run() => _window.Run();