diff options
| author | riperiperi <rhy3756547@hotmail.com> | 2020-12-17 18:39:52 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-17 19:39:52 +0100 |
| commit | 10aa11ce13291cf2ea2aeb751838c65c45fdc0ba (patch) | |
| tree | 3ace1ad77af0fdbfc37ffdee86c24afb5fec97c8 /Ryujinx.HLE/HOS | |
| parent | eae39f80e7ca9ab220e9884c2457c934c2488e00 (diff) | |
Interrupt GPU command processing when a frame's fence is reached. (#1741)
* Interrupt GPU command processing when a frame's fence is reached.
* Accumulate times rather than %s
* Accurate timer for vsync
Spin wait for the last .667ms of a frame. Avoids issues caused by signalling 16ms vsync. (periodic stutters in smo)
* Use event wait for better timing.
* Fix lazy wait
Windows doesn't seem to want to do 1ms consistently, so force a spin if we're less than 2ms.
* A bit more efficiency on frame waits.
Should now wait the remainder 0.6667 instead of 1.6667 sometimes (odd waits above 1ms are reliable, unlike 1ms waits)
* Better swap interval 0 solution
737 fps without breaking a sweat. Downside: Vsync can no longer be disabled on games that use the event heavily (link's awakening - which is ok since it breaks anyways)
* Fix comment.
* Address Comments.
Diffstat (limited to 'Ryujinx.HLE/HOS')
4 files changed, 78 insertions, 7 deletions
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs index 1043dac9..b8fc71f3 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs @@ -46,6 +46,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger public const int BufferHistoryArraySize = 8; + public event Action BufferQueued; + public BufferQueueCore(Switch device, long pid) { Slots = new BufferSlotArray(); @@ -197,6 +199,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger WaitForLock(); } + public void SignalQueueEvent() + { + BufferQueued?.Invoke(); + } + private void WaitForLock() { if (Active) diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs index 03df04ad..d4227f01 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs @@ -486,6 +486,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger Monitor.PulseAll(_callbackLock); } + Core.SignalQueueEvent(); + return Status.Success; } diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs index bdffb499..1fef6860 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs @@ -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, diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs index 7c5c9ba1..62c53f2d 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs @@ -66,6 +66,13 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return false; } + public void RegisterCallback(GpuContext gpuContext, Action callback) + { + ref NvFence fence = ref NvFences[FenceCount - 1]; + + gpuContext.Synchronization.RegisterCallbackOnSyncpoint(fence.Id, fence.Value, callback); + } + public uint GetFlattenedSize() { return (uint)Unsafe.SizeOf<AndroidFence>(); |
