aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
diff options
context:
space:
mode:
authorThog <me@thog.eu>2020-04-22 06:10:27 +0200
committerGitHub <noreply@github.com>2020-04-22 14:10:27 +1000
commit36749c358d85dd0bc7f1e4a36a0b607fc0f724d5 (patch)
tree0c24eb54082b80e8f15aa8138a3e85c5c4e89675 /Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
parent03711dd7b5d44e20fb45c728803ea6b9599dec87 (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.cs283
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;
+ }
+ }
+}