Ryujinx/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
riperiperi 10aa11ce13
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.
2020-12-17 19:39:52 +01:00

342 lines
9.9 KiB
C#

using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
class BufferQueueCore
{
public BufferSlotArray Slots;
public int OverrideMaxBufferCount;
public bool UseAsyncBuffer;
public bool DequeueBufferCannotBlock;
public PixelFormat DefaultBufferFormat;
public int DefaultWidth;
public int DefaultHeight;
public int DefaultMaxBufferCount;
public int MaxAcquiredBufferCount;
public bool BufferHasBeenQueued;
public ulong FrameCounter;
public NativeWindowTransform TransformHint;
public bool IsAbandoned;
public NativeWindowApi ConnectedApi;
public bool IsAllocating;
public IProducerListener ProducerListener;
public IConsumerListener ConsumerListener;
public bool ConsumerControlledByApp;
public uint ConsumerUsageBits;
public List<BufferItem> Queue;
public BufferInfo[] BufferHistory;
public uint BufferHistoryPosition;
public bool EnableExternalEvent;
public int MaxBufferCountCached;
public readonly object Lock = new object();
private KEvent _waitBufferFreeEvent;
private KEvent _frameAvailableEvent;
public long Owner { get; }
public bool Active { get; private set; }
public const int BufferHistoryArraySize = 8;
public event Action BufferQueued;
public BufferQueueCore(Switch device, long pid)
{
Slots = new BufferSlotArray();
IsAbandoned = false;
OverrideMaxBufferCount = 0;
DequeueBufferCannotBlock = false;
UseAsyncBuffer = false;
DefaultWidth = 1;
DefaultHeight = 1;
DefaultMaxBufferCount = 2;
MaxAcquiredBufferCount = 1;
FrameCounter = 0;
TransformHint = 0;
DefaultBufferFormat = PixelFormat.Rgba8888;
IsAllocating = false;
ProducerListener = null;
ConsumerListener = null;
ConsumerUsageBits = 0;
Queue = new List<BufferItem>();
// TODO: CreateGraphicBufferAlloc?
_waitBufferFreeEvent = new KEvent(device.System.KernelContext);
_frameAvailableEvent = new KEvent(device.System.KernelContext);
Owner = pid;
Active = true;
BufferHistory = new BufferInfo[BufferHistoryArraySize];
EnableExternalEvent = true;
MaxBufferCountCached = 0;
}
public int GetMinUndequeuedBufferCountLocked(bool async)
{
if (!UseAsyncBuffer)
{
return 0;
}
if (DequeueBufferCannotBlock || async)
{
return MaxAcquiredBufferCount + 1;
}
return MaxAcquiredBufferCount;
}
public int GetMinMaxBufferCountLocked(bool async)
{
return GetMinUndequeuedBufferCountLocked(async);
}
public void UpdateMaxBufferCountCachedLocked(int slot)
{
if (MaxBufferCountCached <= slot)
{
MaxBufferCountCached = slot + 1;
}
}
public int GetMaxBufferCountLocked(bool async)
{
int minMaxBufferCount = GetMinMaxBufferCountLocked(async);
int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount);
if (OverrideMaxBufferCount != 0)
{
return OverrideMaxBufferCount;
}
// Preserve all buffers already in control of the producer and the consumer.
for (int slot = maxBufferCount; slot < Slots.Length; slot++)
{
BufferState state = Slots[slot].BufferState;
if (state == BufferState.Queued || state == BufferState.Dequeued)
{
maxBufferCount = slot + 1;
}
}
return maxBufferCount;
}
public Status SetDefaultMaxBufferCountLocked(int count)
{
int minBufferCount = UseAsyncBuffer ? 2 : 1;
if (count < minBufferCount || count > Slots.Length)
{
return Status.BadValue;
}
DefaultMaxBufferCount = count;
SignalDequeueEvent();
return Status.Success;
}
public void SignalWaitBufferFreeEvent()
{
if (EnableExternalEvent)
{
_waitBufferFreeEvent.WritableEvent.Signal();
}
}
public void SignalFrameAvailableEvent()
{
if (EnableExternalEvent)
{
_frameAvailableEvent.WritableEvent.Signal();
}
}
public void PrepareForExit()
{
lock (Lock)
{
Active = false;
Monitor.PulseAll(Lock);
}
}
// TODO: Find an accurate way to handle a regular condvar here as this will wake up unwanted threads in some edge cases.
public void SignalDequeueEvent()
{
Monitor.PulseAll(Lock);
}
public void WaitDequeueEvent()
{
WaitForLock();
}
public void SignalIsAllocatingEvent()
{
Monitor.PulseAll(Lock);
}
public void WaitIsAllocatingEvent()
{
WaitForLock();
}
public void SignalQueueEvent()
{
BufferQueued?.Invoke();
}
private void WaitForLock()
{
if (Active)
{
Monitor.Wait(Lock);
}
}
public void FreeBufferLocked(int slot)
{
Slots[slot].GraphicBuffer.Reset();
if (Slots[slot].BufferState == BufferState.Acquired)
{
Slots[slot].NeedsCleanupOnRelease = true;
}
Slots[slot].BufferState = BufferState.Free;
Slots[slot].FrameNumber = uint.MaxValue;
Slots[slot].AcquireCalled = false;
Slots[slot].Fence.FenceCount = 0;
}
public void FreeAllBuffersLocked()
{
BufferHasBeenQueued = false;
for (int slot = 0; slot < Slots.Length; slot++)
{
FreeBufferLocked(slot);
}
}
public bool StillTracking(ref BufferItem item)
{
BufferSlot slot = Slots[item.Slot];
// TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be.
return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
}
public void WaitWhileAllocatingLocked()
{
while (IsAllocating)
{
WaitIsAllocatingEvent();
}
}
public void CheckSystemEventsLocked(int maxBufferCount)
{
if (!EnableExternalEvent)
{
return;
}
bool needBufferReleaseSignal = false;
bool needFrameAvailableSignal = false;
if (maxBufferCount > 1)
{
for (int i = 0; i < maxBufferCount; i++)
{
if (Slots[i].BufferState == BufferState.Queued)
{
needFrameAvailableSignal = true;
}
else if (Slots[i].BufferState == BufferState.Free)
{
needBufferReleaseSignal = true;
}
}
}
if (needBufferReleaseSignal)
{
SignalWaitBufferFreeEvent();
}
else
{
_waitBufferFreeEvent.WritableEvent.Clear();
}
if (needFrameAvailableSignal)
{
SignalFrameAvailableEvent();
}
else
{
_frameAvailableEvent.WritableEvent.Clear();
}
}
public bool IsProducerConnectedLocked()
{
return ConnectedApi != NativeWindowApi.NoApi;
}
public bool IsConsumerConnectedLocked()
{
return ConsumerListener != null;
}
public KReadableEvent GetWaitBufferFreeEvent()
{
lock (Lock)
{
return _waitBufferFreeEvent.ReadableEvent;
}
}
public bool IsOwnedByConsumerLocked(int slot)
{
if (Slots[slot].BufferState != BufferState.Acquired)
{
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the consumer (state = {Slots[slot].BufferState})");
return false;
}
return true;
}
public bool IsOwnedByProducerLocked(int slot)
{
if (Slots[slot].BufferState != BufferState.Dequeued)
{
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})");
return false;
}
return true;
}
}
}