diff options
| author | Thog <me@thog.eu> | 2020-04-22 06:10:27 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-04-22 14:10:27 +1000 |
| commit | 36749c358d85dd0bc7f1e4a36a0b607fc0f724d5 (patch) | |
| tree | 0c24eb54082b80e8f15aa8138a3e85c5c4e89675 /Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs | |
| parent | 03711dd7b5d44e20fb45c728803ea6b9599dec87 (diff) | |
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
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs')
| -rw-r--r-- | Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs new file mode 100644 index 00000000..8a5f5831 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs @@ -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; + } + } +} |
