10aa11ce13
* 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.
342 lines
9.9 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|