SurfaceFlinger v2 (#981)
* Rewrite SurfaceFlinger Reimplement accurately SurfaceFlinger (based on my 8.1.0 reversing of it) TODO: support swap interval properly and reintroduce disabled "game vsync" support. * Some fixes for SetBufferCount * uncomment a test from last commit * SurfaceFlinger: don't free the graphic buffer in SetBufferCount * SurfaceFlinger: Implement swap interval correctly * SurfaceFlinger: Reintegrate Game VSync toggle * SurfaceFlinger: do not push a fence on buffer release on the consumer side * Revert "SurfaceFlinger: do not push a fence on buffer release on the consumer side" This reverts commit 586b52b0bfab2d11f361f4b59ab7b7141020bbad. * Make the game vsync toggle work dynamically again * Unregister producer's Binder object when closing layer * Address ripinperi's comments * Add a timeout on syncpoint wait operation Syncpoint aren't supposed to be waited on for more than a second. This effectively workaround issues caused by not having a channel scheduling in place yet. PS: Also introduce Android WaitForever warning about fence being not signaled for 3s * Fix a print of previous commit * Address Ac_K's comments * Address gdkchan's comments * Address final comments
This commit is contained in:
parent
03711dd7b5
commit
36749c358d
50 changed files with 3416 additions and 831 deletions
283
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
Normal file
283
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
Normal file
|
@ -0,0 +1,283 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
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 readonly object Lock = new object();
|
||||
|
||||
private KEvent _waitBufferFreeEvent;
|
||||
private KEvent _frameAvailableEvent;
|
||||
|
||||
public KProcess Owner { get; }
|
||||
|
||||
public BufferQueueCore(Switch device, KProcess process)
|
||||
{
|
||||
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);
|
||||
_frameAvailableEvent = new KEvent(device.System);
|
||||
|
||||
Owner = process;
|
||||
}
|
||||
|
||||
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 int GetMaxBufferCountLocked(bool async)
|
||||
{
|
||||
int minMaxBufferCount = GetMinMaxBufferCountLocked(async);
|
||||
|
||||
int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount);
|
||||
|
||||
if (OverrideMaxBufferCount != 0)
|
||||
{
|
||||
maxBufferCount = 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()
|
||||
{
|
||||
_waitBufferFreeEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
public void SignalFrameAvailableEvent()
|
||||
{
|
||||
_frameAvailableEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
// 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()
|
||||
{
|
||||
Monitor.Wait(Lock);
|
||||
}
|
||||
|
||||
public void SignalIsAbandonedEvent()
|
||||
{
|
||||
Monitor.PulseAll(Lock);
|
||||
}
|
||||
|
||||
public void WaitIsAbandonedEvent()
|
||||
{
|
||||
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 (IsAbandoned)
|
||||
{
|
||||
WaitIsAbandonedEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckSystemEventsLocked(int maxBufferCount)
|
||||
{
|
||||
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.PrintError(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.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue