From 378259a40a938cad6a53b7d2910bb1d2d0fdb58a Mon Sep 17 00:00:00 2001
From: Thog <me@thog.eu>
Date: Fri, 15 May 2020 03:30:08 +0200
Subject: [PATCH] Surface Flinger: Implement GetBufferHistory (#1232)

* Surface Flinger: Implement GetBufferHistory

Also fix some bugs on the Surface Flinger implementation

* Address Ac_K's comment
---
 ARMeilleure/State/ExecutionContext.cs         |  2 +
 .../SurfaceFlinger/BufferQueueConsumer.cs     | 46 ++++++++++++++++
 .../SurfaceFlinger/BufferQueueCore.cs         | 24 ++++++++-
 .../SurfaceFlinger/BufferQueueProducer.cs     | 52 ++++++++++++++++++-
 .../HOS/Services/SurfaceFlinger/BufferSlot.cs |  9 +++-
 .../SurfaceFlinger/IGraphicBufferProducer.cs  | 14 +++++
 .../SurfaceFlinger/IHOSBinderDriver.cs        |  2 +-
 .../HOS/Services/SurfaceFlinger/Parcel.cs     |  5 ++
 .../Services/SurfaceFlinger/SurfaceFlinger.cs |  2 +-
 .../SurfaceFlinger/Types/BufferInfo.cs        | 14 +++++
 .../SurfaceFlinger/{ => Types}/BufferItem.cs  |  0
 .../Services/Time/Clock/Types/TimeSpanType.cs |  5 ++
 12 files changed, 167 insertions(+), 8 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs
 rename Ryujinx.HLE/HOS/Services/SurfaceFlinger/{ => Types}/BufferItem.cs (100%)

diff --git a/ARMeilleure/State/ExecutionContext.cs b/ARMeilleure/State/ExecutionContext.cs
index 866eafbc7b..8593f41e4b 100644
--- a/ARMeilleure/State/ExecutionContext.cs
+++ b/ARMeilleure/State/ExecutionContext.cs
@@ -32,6 +32,8 @@ namespace ARMeilleure.State
             }
         }
 
+        public static TimeSpan ElapsedTime => _tickCounter.Elapsed;
+
         public long TpidrEl0 { get; set; }
         public long Tpidr    { get; set; }
 
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs
index 54ba56702b..fcbbf5f346 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs
@@ -1,5 +1,6 @@
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
 using System;
 
 namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
@@ -57,6 +58,18 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                     Core.Slots[bufferItem.Slot].NeedsCleanupOnRelease = true;
                     Core.Slots[bufferItem.Slot].BufferState           = BufferState.Acquired;
                     Core.Slots[bufferItem.Slot].Fence                 = AndroidFence.NoFence;
+
+                    ulong targetFrameNumber = Core.Slots[bufferItem.Slot].FrameNumber;
+
+                    for (int i = 0; i < Core.BufferHistory.Length; i++)
+                    {
+                        if (Core.BufferHistory[i].FrameNumber == targetFrameNumber)
+                        {
+                            Core.BufferHistory[i].State = BufferState.Acquired;
+
+                            break;
+                        }
+                    }
                 }
 
                 if (bufferItem.AcquireCalled)
@@ -368,5 +381,38 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 
             return Status.Success;
         }
+
+        public Status SetPresentTime(int slot, ulong frameNumber, TimeSpanType presentationTime)
+        {
+            if (slot < 0 || slot >= Core.Slots.Length)
+            {
+                return Status.BadValue;
+            }
+
+            lock (Core.Lock)
+            {
+                if (Core.Slots[slot].FrameNumber != frameNumber)
+                {
+                    return Status.StaleBufferSlot;
+                }
+
+                if (Core.Slots[slot].PresentationTime.NanoSeconds == 0)
+                {
+                    Core.Slots[slot].PresentationTime = presentationTime;
+                }
+
+                for (int i = 0; i < Core.BufferHistory.Length; i++)
+                {
+                    if (Core.BufferHistory[i].FrameNumber == frameNumber)
+                    {
+                        Core.BufferHistory[i].PresentationTime = presentationTime;
+
+                        break;
+                    }
+                }
+            }
+
+            return Status.Success;
+        }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
index efc2523961..70d72c5a58 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
@@ -1,6 +1,7 @@
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
 using System;
 using System.Collections.Generic;
 using System.Threading;
@@ -29,6 +30,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
         public bool                  ConsumerControlledByApp;
         public uint                  ConsumerUsageBits;
         public List<BufferItem>      Queue;
+        public BufferInfo[]          BufferHistory;
+        public uint                  BufferHistoryPosition;
+        public bool                  EnableExternalEvent;
 
         public readonly object Lock = new object();
 
@@ -37,6 +41,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 
         public KProcess Owner { get; }
 
+        public const int BufferHistoryArraySize = 8;
+
         public BufferQueueCore(Switch device, KProcess process)
         {
             Slots                    = new BufferSlotArray();
@@ -64,6 +70,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
             _frameAvailableEvent = new KEvent(device.System.KernelContext);
 
             Owner = process;
+
+            BufferHistory       = new BufferInfo[BufferHistoryArraySize];
+            EnableExternalEvent = true;
         }
 
         public int GetMinUndequeuedBufferCountLocked(bool async)
@@ -129,12 +138,18 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 
         public void SignalWaitBufferFreeEvent()
         {
-            _waitBufferFreeEvent.WritableEvent.Signal();
+            if (EnableExternalEvent)
+            {
+                _waitBufferFreeEvent.WritableEvent.Signal();
+            }
         }
 
         public void SignalFrameAvailableEvent()
         {
-            _frameAvailableEvent.WritableEvent.Signal();
+            if (EnableExternalEvent)
+            {
+                _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.
@@ -201,6 +216,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 
         public void CheckSystemEventsLocked(int maxBufferCount)
         {
+            if (!EnableExternalEvent)
+            {
+                return;
+            }
+
             bool needBufferReleaseSignal  = false;
             bool needFrameAvailableSignal = false;
 
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs
index 333bffd121..fb5d219407 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs
@@ -1,7 +1,7 @@
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
 using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
 using System;
 using System.Threading;
 
@@ -92,8 +92,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                     return Status.BadValue;
                 }
 
+                Core.Queue.Clear();
+                Core.FreeAllBuffersLocked();
                 Core.OverrideMaxBufferCount = bufferCount;
                 Core.SignalDequeueEvent();
+                Core.SignalWaitBufferFreeEvent();
 
                 listener = Core.ConsumerListener;
             }
@@ -363,7 +366,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                 Core.Slots[slot].Fence       = input.Fence;
                 Core.Slots[slot].BufferState = BufferState.Queued;
                 Core.FrameCounter++;
-                Core.Slots[slot].FrameNumber = Core.FrameCounter;
+                Core.Slots[slot].FrameNumber      = Core.FrameCounter;
+                Core.Slots[slot].QueueTime        = TimeSpanType.FromTimeSpan(ARMeilleure.State.ExecutionContext.ElapsedTime);
+                Core.Slots[slot].PresentationTime = TimeSpanType.Zero;
 
                 item.AcquireCalled             = Core.Slots[slot].AcquireCalled;
                 item.Crop                      = input.Crop;
@@ -381,6 +386,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                 item.GraphicBuffer.Set(Core.Slots[slot].GraphicBuffer);
                 item.GraphicBuffer.Object.IncrementNvMapHandleRefCount(Core.Owner);
 
+                Core.BufferHistoryPosition = (Core.BufferHistoryPosition + 1) % BufferQueueCore.BufferHistoryArraySize;
+
+                Core.BufferHistory[Core.BufferHistoryPosition] = new BufferInfo
+                {
+                    FrameNumber = Core.FrameCounter,
+                    QueueTime   = Core.Slots[slot].QueueTime,
+                    State       = BufferState.Queued
+                };
+
                 _stickyTransform = input.StickyTransform;
 
                 if (Core.Queue.Count == 0)
@@ -488,6 +502,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                     case NativeWindowAttribute.MinUnqueuedBuffers:
                         outValue = Core.GetMinUndequeuedBufferCountLocked(false);
                         return Status.Success;
+                    case NativeWindowAttribute.ConsumerRunningBehind:
+                        outValue = Core.Queue.Count > 1 ? 1 : 0;
+                        return Status.Success;
                     case NativeWindowAttribute.ConsumerUsageBits:
                         outValue = (int)Core.ConsumerUsageBits;
                         return Status.Success;
@@ -561,6 +578,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                     case NativeWindowApi.Camera:
                         if (Core.ConnectedApi == api)
                         {
+                            Core.Queue.Clear();
                             Core.FreeAllBuffersLocked();
 
                             producerListener = Core.ProducerListener;
@@ -762,5 +780,35 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
         {
             return Core.GetWaitBufferFreeEvent();
         }
+
+        public override Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos)
+        {
+            if (bufferHistoryCount <= 0)
+            {
+                bufferInfos = Span<BufferInfo>.Empty;
+
+                return Status.BadValue;
+            }
+
+            lock (Core.Lock)
+            {
+                bufferHistoryCount = Math.Min(bufferHistoryCount, Core.BufferHistory.Length);
+
+                BufferInfo[] result = new BufferInfo[bufferHistoryCount];
+
+                uint position = Core.BufferHistoryPosition;
+
+                for (uint i = 0; i < bufferHistoryCount; i++)
+                {
+                    result[i] = Core.BufferHistory[(position - i) % Core.BufferHistory.Length];
+
+                    position--;
+                }
+
+                bufferInfos = result;
+
+                return Status.Success;
+            }
+        }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
index dbfba0ee8f..2f17f8a2cb 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
@@ -1,4 +1,5 @@
 using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
 
 namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 {
@@ -12,11 +13,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
         public bool                                AcquireCalled;
         public bool                                NeedsCleanupOnRelease;
         public bool                                AttachedByConsumer;
+        public TimeSpanType                        QueueTime;
+        public TimeSpanType                        PresentationTime;
 
         public BufferSlot()
         {
-            GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
-            BufferState   = BufferState.Free;
+            GraphicBuffer    = new AndroidStrongPointer<GraphicBuffer>();
+            BufferState      = BufferState.Free;
+            QueueTime        = TimeSpanType.Zero;
+            PresentationTime = TimeSpanType.Zero;
         }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs
index 41e21648ea..3b4996c8ae 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs
@@ -236,6 +236,18 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 
                     outputParcel.WriteStatus(status);
 
+                    break;
+                case TransactionCode.GetBufferHistory:
+                    int bufferHistoryCount = inputParcel.ReadInt32();
+
+                    status = GetBufferHistory(bufferHistoryCount, out Span<BufferInfo> bufferInfos);
+
+                    outputParcel.WriteStatus(status);
+
+                    outputParcel.WriteInt32(bufferInfos.Length);
+
+                    outputParcel.WriteUnmanagedSpan<BufferInfo>(bufferInfos);
+
                     break;
                 default:
                     throw new NotImplementedException($"Transaction {(TransactionCode)code} not implemented");
@@ -272,5 +284,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
         public abstract Status Disconnect(NativeWindowApi api);
 
         public abstract Status SetPreallocatedBuffer(int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer);
+
+        public abstract Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos);
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
index 5546418fbe..8ee943df80 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
@@ -74,7 +74,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
         public ResultCode TransactParcelAuto(ServiceCtx context)
         {
             int binderId = context.RequestData.ReadInt32();
-            
+
             uint code  = context.RequestData.ReadUInt32();
             uint flags = context.RequestData.ReadUInt32();
 
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
index 3def026b87..fbe38c08a5 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
@@ -173,6 +173,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
         public void WriteInt64(long value) => WriteUnmanagedType(ref value);
         public void WriteUInt64(ulong value) => WriteUnmanagedType(ref value);
 
+        public void WriteUnmanagedSpan<T>(ReadOnlySpan<T> value) where T : unmanaged
+        {
+            WriteInplace(MemoryMarshal.Cast<T, byte>(value));
+        }
+
         public void WriteUnmanagedType<T>(ref T value) where T : unmanaged
         {
             WriteInplace(SpanHelpers.AsByteSpan(ref value));
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
index 784f8c466a..e1eb55ade7 100644
--- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
@@ -198,7 +198,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                 {
                     Compose();
 
-                    _device.System.SignalVsync();
+                    _device.System?.SignalVsync();
 
                     _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
                 }
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs
new file mode 100644
index 0000000000..12c41b0d42
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x1C, Pack = 1)]
+    struct BufferInfo
+    {
+        public ulong        FrameNumber;
+        public TimeSpanType QueueTime;
+        public TimeSpanType PresentationTime;
+        public BufferState  State;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItem.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs
similarity index 100%
rename from Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItem.cs
rename to Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
index 89497d824f..94c1b4885b 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
@@ -39,6 +39,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
             return new TimeSpanType(seconds * NanoSecondsPerSecond);
         }
 
+        public static TimeSpanType FromTimeSpan(TimeSpan timeSpan)
+        {
+            return new TimeSpanType((long)(timeSpan.TotalMilliseconds * 1000000));
+        }
+
         public static TimeSpanType FromTicks(ulong ticks, ulong frequency)
         {
             return FromSeconds((long)ticks / (long)frequency);