|
|
@@ -25,8 +25,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|
|
|
|
|
private Stopwatch _chrono;
|
|
|
|
|
|
+ private ManualResetEvent _event = new ManualResetEvent(false);
|
|
|
+ private AutoResetEvent _nextFrameEvent = new AutoResetEvent(true);
|
|
|
private long _ticks;
|
|
|
private long _ticksPerFrame;
|
|
|
+ private long _spinTicks;
|
|
|
+ private long _1msTicks;
|
|
|
|
|
|
private int _swapInterval;
|
|
|
|
|
|
@@ -61,8 +65,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|
|
};
|
|
|
|
|
|
_chrono = new Stopwatch();
|
|
|
+ _chrono.Start();
|
|
|
|
|
|
_ticks = 0;
|
|
|
+ _spinTicks = Stopwatch.Frequency / 500;
|
|
|
+ _1msTicks = Stopwatch.Frequency / 1000;
|
|
|
|
|
|
UpdateSwapInterval(1);
|
|
|
|
|
|
@@ -76,6 +83,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|
|
// If the swap interval is 0, Game VSync is disabled.
|
|
|
if (_swapInterval == 0)
|
|
|
{
|
|
|
+ _nextFrameEvent.Set();
|
|
|
_ticksPerFrame = 1;
|
|
|
}
|
|
|
else
|
|
|
@@ -129,6 +137,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|
|
|
|
|
BufferQueueCore core = BufferQueue.CreateBufferQueue(_device, pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer);
|
|
|
|
|
|
+ core.BufferQueued += () =>
|
|
|
+ {
|
|
|
+ _nextFrameEvent.Set();
|
|
|
+ };
|
|
|
+
|
|
|
_layers.Add(layerId, new Layer
|
|
|
{
|
|
|
ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer),
|
|
|
@@ -189,23 +202,59 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|
|
{
|
|
|
_isRunning = true;
|
|
|
|
|
|
+ long lastTicks = _chrono.ElapsedTicks;
|
|
|
+
|
|
|
while (_isRunning)
|
|
|
{
|
|
|
- _ticks += _chrono.ElapsedTicks;
|
|
|
+ long ticks = _chrono.ElapsedTicks;
|
|
|
|
|
|
- _chrono.Restart();
|
|
|
-
|
|
|
- if (_ticks >= _ticksPerFrame)
|
|
|
+ if (_swapInterval == 0)
|
|
|
{
|
|
|
Compose();
|
|
|
|
|
|
_device.System?.SignalVsync();
|
|
|
|
|
|
- _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
|
|
|
+ _nextFrameEvent.WaitOne(17);
|
|
|
+ lastTicks = ticks;
|
|
|
}
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _ticks += ticks - lastTicks;
|
|
|
+ lastTicks = ticks;
|
|
|
+
|
|
|
+ if (_ticks >= _ticksPerFrame)
|
|
|
+ {
|
|
|
+ Compose();
|
|
|
+
|
|
|
+ _device.System?.SignalVsync();
|
|
|
+
|
|
|
+ // Apply a maximum bound of 3 frames to the tick remainder, in case some event causes Ryujinx to pause for a long time or messes with the timer.
|
|
|
+ _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame * 3);
|
|
|
+ }
|
|
|
|
|
|
- // Sleep the minimal amount of time to avoid being too expensive.
|
|
|
- Thread.Sleep(1);
|
|
|
+ // Sleep if possible. If the time til the next frame is too low, spin wait instead.
|
|
|
+ long diff = _ticksPerFrame - (_ticks + _chrono.ElapsedTicks - ticks);
|
|
|
+ if (diff > 0)
|
|
|
+ {
|
|
|
+ if (diff < _spinTicks)
|
|
|
+ {
|
|
|
+ do
|
|
|
+ {
|
|
|
+ // SpinWait is a little more HT/SMT friendly than aggressively updating/checking ticks.
|
|
|
+ // The value of 5 still gives us quite a bit of precision (~0.0003ms variance at worst) while waiting a reasonable amount of time.
|
|
|
+ Thread.SpinWait(5);
|
|
|
+
|
|
|
+ ticks = _chrono.ElapsedTicks;
|
|
|
+ _ticks += ticks - lastTicks;
|
|
|
+ lastTicks = ticks;
|
|
|
+ } while (_ticks < _ticksPerFrame);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _event.WaitOne((int)(diff / _1msTicks));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -299,6 +348,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|
|
Item = item,
|
|
|
};
|
|
|
|
|
|
+ item.Fence.RegisterCallback(_device.Gpu, () =>
|
|
|
+ {
|
|
|
+ _device.Gpu.Window.SignalFrameReady();
|
|
|
+ _device.Gpu.GPFifo.Interrupt();
|
|
|
+ });
|
|
|
+
|
|
|
_device.Gpu.Window.EnqueueFrameThreadSafe(
|
|
|
frameBufferAddress,
|
|
|
frameBufferWidth,
|