Quellcode durchsuchen

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
Thog vor 6 Jahren
Ursprung
Commit
36749c358d
50 geänderte Dateien mit 3415 neuen und 830 gelöschten Zeilen
  1. 2 1
      Ryujinx.Common/Logging/LogClass.cs
  2. 17 1
      Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs
  3. 6 6
      Ryujinx.HLE/HOS/Horizon.cs
  4. 7 0
      Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs
  5. 31 7
      Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs
  6. 5 0
      Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs
  7. 62 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItem.cs
  8. 95 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs
  9. 15 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs
  10. 372 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs
  11. 283 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
  12. 752 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs
  13. 22 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
  14. 28 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs
  15. 175 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs
  16. 109 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs
  17. 41 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs
  18. 9 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs
  19. 13 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs
  20. 276 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs
  21. 104 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
  22. 7 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs
  23. 11 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs
  24. 13 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs
  25. 11 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs
  26. 16 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs
  27. 0 455
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs
  28. 189 31
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
  29. 10 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs
  30. 14 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs
  31. 22 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs
  32. 376 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
  33. 89 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs
  34. 38 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs
  35. 0 15
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs
  36. 5 5
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs
  37. 0 37
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs
  38. 75 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs
  39. 7 7
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs
  40. 0 14
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs
  41. 0 49
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs
  42. 1 1
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs
  43. 2 0
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs
  44. 0 35
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs
  45. 61 3
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs
  46. 0 99
      Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs
  47. 9 5
      Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs
  48. 35 50
      Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs
  49. 0 5
      Ryujinx.HLE/Switch.cs
  50. 0 4
      Ryujinx/Ui/GLRenderer.cs

+ 2 - 1
Ryujinx.Common/Logging/LogClass.cs

@@ -46,6 +46,7 @@ namespace Ryujinx.Common.Logging
         ServiceSsl,
         ServiceSss,
         ServiceTime,
-        ServiceVi
+        ServiceVi,
+        SurfaceFlinger
     }
 }

+ 17 - 1
Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs

@@ -1,4 +1,5 @@
-using System;
+using Ryujinx.Common.Logging;
+using System;
 using System.Threading;
 
 namespace Ryujinx.Graphics.Gpu.Synchronization
@@ -111,6 +112,16 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
                 throw new ArgumentOutOfRangeException(nameof(id));
             }
 
+            bool warnAboutTimeout = false;
+
+            // TODO: Remove this when GPU channel scheduling will be implemented.
+            if (timeout == Timeout.InfiniteTimeSpan)
+            {
+                timeout = TimeSpan.FromSeconds(1);
+
+                warnAboutTimeout = true;
+            }
+
             using (ManualResetEvent waitEvent = new ManualResetEvent(false))
             {
                 var info = _syncpoints[id].RegisterCallback(threshold, () => waitEvent.Set());
@@ -124,6 +135,11 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
 
                 if (!signaled && info != null)
                 {
+                    if (warnAboutTimeout)
+                    {
+                        Logger.PrintError(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms, resuming execution...");
+                    }
+
                     _syncpoints[id].UnregisterCallback(info);
                 }
 

+ 6 - 6
Ryujinx.HLE/HOS/Horizon.cs

@@ -66,6 +66,8 @@ namespace Ryujinx.HLE.HOS
 
         internal Switch Device { get; private set; }
 
+        internal SurfaceFlinger SurfaceFlinger { get; private set; }
+
         public SystemStateMgr State { get; private set; }
 
         internal bool KernelInitialized { get; private set; }
@@ -268,6 +270,8 @@ namespace Ryujinx.HLE.HOS
             DatabaseImpl.Instance.InitializeDatabase(device);
 
             HostSyncpoint = new NvHostSyncpt(device);
+
+            SurfaceFlinger = new SurfaceFlinger(device);
         }
 
         public void LoadCart(string exeFsDir, string romFsFile = null)
@@ -850,6 +854,8 @@ namespace Ryujinx.HLE.HOS
             {
                 _isDisposed = true;
 
+                SurfaceFlinger.Dispose();
+
                 KProcess terminationProcess = new KProcess(this);
 
                 KThread terminationThread = new KThread(this);
@@ -873,12 +879,6 @@ namespace Ryujinx.HLE.HOS
 
                 terminationThread.Start();
 
-                // Signal the vsync event to avoid issues of KThread waiting on it.
-                if (Device.EnableDeviceVsync)
-                {
-                    Device.VsyncEvent.Set();
-                }
-
                 // Destroy nvservices channels as KThread could be waiting on some user events.
                 // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade.
                 INvDrvServices.Destroy();

+ 7 - 0
Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs

@@ -8,6 +8,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
 {
     class NvHostSyncpt
     {
+        public const int VBlank0SyncpointId = 26;
+        public const int VBlank1SyncpointId = 27;
+
         private int[]  _counterMin;
         private int[]  _counterMax;
         private bool[] _clientManaged;
@@ -24,6 +27,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
             _counterMax    = new int[SynchronizationManager.MaxHardwareSyncpoints];
             _clientManaged = new bool[SynchronizationManager.MaxHardwareSyncpoints];
             _assigned      = new bool[SynchronizationManager.MaxHardwareSyncpoints];
+
+            // Reserve VBLANK syncpoints
+            ReserveSyncpointLocked(VBlank0SyncpointId, true);
+            ReserveSyncpointLocked(VBlank1SyncpointId, true);
         }
 
         private void ReserveSyncpointLocked(uint id, bool isClientManaged)

+ 31 - 7
Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs

@@ -165,12 +165,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
                 return NvInternalResult.InvalidInput;
             }
 
-            if (map.DecrementRefCount() <= 0)
+            if (DecrementMapRefCount(Owner, arguments.Handle))
             {
-                DeleteMapWithHandle(arguments.Handle);
-
-                Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {arguments.Handle}!");
-
                 arguments.Address = map.Address;
                 arguments.Flags   = 0;
             }
@@ -248,9 +244,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
             return dict.Add(map);
         }
 
-        private bool DeleteMapWithHandle(int handle)
+        private static bool DeleteMapWithHandle(KProcess process, int handle)
         {
-            if (_maps.TryGetValue(Owner, out IdDictionary dict))
+            if (_maps.TryGetValue(process, out IdDictionary dict))
             {
                 return dict.Delete(handle) != null;
             }
@@ -258,6 +254,34 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
             return false;
         }
 
+        public static void IncrementMapRefCount(KProcess process, int handle, bool allowHandleZero = false)
+        {
+            GetMapFromHandle(process, handle, allowHandleZero)?.IncrementRefCount();
+        }
+
+        public static bool DecrementMapRefCount(KProcess process, int handle)
+        {
+            NvMapHandle map = GetMapFromHandle(process, handle, false);
+
+            if (map == null)
+            {
+                return false;
+            }
+
+            if (map.DecrementRefCount() <= 0)
+            {
+                DeleteMapWithHandle(process, handle);
+
+                Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {handle}!");
+
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
         public static NvMapHandle GetMapFromHandle(KProcess process, int handle, bool allowHandleZero = false)
         {
             if ((allowHandleZero || handle != 0) && _maps.TryGetValue(process, out IdDictionary dict))

+ 5 - 0
Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs

@@ -23,6 +23,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.Types
             Value = hostSyncpt.ReadSyncpointValue(Id);
         }
 
+        public void Increment(GpuContext gpuContext)
+        {
+            Value = gpuContext.Synchronization.IncrementSyncpoint(Id);
+        }
+
         public bool Wait(GpuContext gpuContext, TimeSpan timeout)
         {
             if (IsValid())

+ 62 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItem.cs

@@ -0,0 +1,62 @@
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class BufferItem : ICloneable
+    {
+        public AndroidStrongPointer<GraphicBuffer> GraphicBuffer;
+        public AndroidFence                        Fence;
+        public Rect                                Crop;
+        public NativeWindowTransform               Transform;
+        public NativeWindowScalingMode             ScalingMode;
+        public long                                Timestamp;
+        public bool                                IsAutoTimestamp;
+        public int                                 SwapInterval;
+        public ulong                               FrameNumber;
+        public int                                 Slot;
+        public bool                                IsDroppable;
+        public bool                                AcquireCalled;
+        public bool                                TransformToDisplayInverse;
+
+        public BufferItem()
+        {
+            GraphicBuffer             = new AndroidStrongPointer<GraphicBuffer>();
+            Transform                 = NativeWindowTransform.None;
+            ScalingMode               = NativeWindowScalingMode.Freeze;
+            Timestamp                 = 0;
+            IsAutoTimestamp           = false;
+            FrameNumber               = 0;
+            Slot                      = BufferSlotArray.InvalidBufferSlot;
+            IsDroppable               = false;
+            AcquireCalled             = false;
+            TransformToDisplayInverse = false;
+            SwapInterval              = 1;
+            Fence                     = AndroidFence.NoFence;
+
+            Crop = new Rect();
+            Crop.MakeInvalid();
+        }
+
+        public object Clone()
+        {
+            BufferItem item = new BufferItem();
+
+            item.Transform                 = Transform;
+            item.ScalingMode               = ScalingMode;
+            item.IsAutoTimestamp           = IsAutoTimestamp;
+            item.FrameNumber               = FrameNumber;
+            item.Slot                      = Slot;
+            item.IsDroppable               = IsDroppable;
+            item.AcquireCalled             = AcquireCalled;
+            item.TransformToDisplayInverse = TransformToDisplayInverse;
+            item.SwapInterval              = SwapInterval;
+            item.Fence                     = Fence;
+            item.Crop                      = Crop;
+
+            item.GraphicBuffer.Set(GraphicBuffer);
+
+            return item;
+        }
+    }
+}

+ 95 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs

@@ -0,0 +1,95 @@
+using Ryujinx.Graphics.Gpu;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class BufferItemConsumer : ConsumerBase
+    {
+        private GpuContext _gpuContext;
+
+        public BufferItemConsumer(Switch device,
+            BufferQueueConsumer consumer,
+            uint consumerUsage,
+            int bufferCount,
+            bool controlledByApp,
+            IConsumerListener listener = null) : base(consumer, controlledByApp, listener)
+        {
+            _gpuContext = device.Gpu;
+
+            Status status = Consumer.SetConsumerUsageBits(consumerUsage);
+
+            if (status != Status.Success)
+            {
+                throw new InvalidOperationException();
+            }
+
+            if (bufferCount != -1)
+            {
+                status = Consumer.SetMaxAcquiredBufferCount(bufferCount);
+
+                if (status != Status.Success)
+                {
+                    throw new InvalidOperationException();
+                }
+            }
+        }
+
+        public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent, bool waitForFence = false)
+        {
+            lock (Lock)
+            {
+                Status status = AcquireBufferLocked(out BufferItem tmp, expectedPresent);
+
+                if (status != Status.Success)
+                {
+                    bufferItem = null;
+
+                    return status;
+                }
+
+                // Make sure to clone the object to not temper the real instance.
+                bufferItem = (BufferItem)tmp.Clone();
+
+                if (waitForFence)
+                {
+                    bufferItem.Fence.WaitForever(_gpuContext);
+                }
+
+                bufferItem.GraphicBuffer.Set(Slots[bufferItem.Slot].GraphicBuffer);
+
+                return Status.Success;
+            }
+        }
+
+        public Status ReleaseBuffer(BufferItem bufferItem, ref AndroidFence fence)
+        {
+            lock (Lock)
+            {
+                Status result = AddReleaseFenceLocked(bufferItem.Slot, ref bufferItem.GraphicBuffer, ref fence);
+
+                if (result == Status.Success)
+                {
+                    result = ReleaseBufferLocked(bufferItem.Slot, ref bufferItem.GraphicBuffer);
+                }
+
+                return result;
+            }
+        }
+
+        public Status SetDefaultBufferSize(uint width, uint height)
+        {
+            lock (Lock)
+            {
+                return Consumer.SetDefaultBufferSize(width, height);
+            }
+        }
+
+        public Status SetDefaultBufferFormat(PixelFormat defaultFormat)
+        {
+            lock (Lock)
+            {
+                return Consumer.SetDefaultBufferFormat(defaultFormat);
+            }
+        }
+    }
+}

+ 15 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs

@@ -0,0 +1,15 @@
+using Ryujinx.HLE.HOS.Kernel.Process;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class BufferQueue
+    {
+        public static void CreateBufferQueue(Switch device, KProcess process, out BufferQueueProducer producer, out BufferQueueConsumer consumer)
+        {
+            BufferQueueCore core = new BufferQueueCore(device, process);
+
+            producer = new BufferQueueProducer(core);
+            consumer = new BufferQueueConsumer(core);
+        }
+    }
+}

+ 372 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs

@@ -0,0 +1,372 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class BufferQueueConsumer
+    {
+        public BufferQueueCore Core { get; }
+
+        public BufferQueueConsumer(BufferQueueCore core)
+        {
+            Core = core;
+        }
+
+        public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent)
+        {
+            lock (Core.Lock)
+            {
+                int numAcquiredBuffers = 0;
+
+                for (int i = 0; i < Core.Slots.Length; i++)
+                {
+                    if (Core.Slots[i].BufferState == BufferState.Acquired)
+                    {
+                        numAcquiredBuffers++;
+                    }
+                }
+
+                if (numAcquiredBuffers >= Core.MaxAcquiredBufferCount + 1)
+                {
+                    bufferItem = null;
+
+                    Logger.PrintDebug(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})");
+
+                    return Status.InvalidOperation;
+                }
+
+                if (Core.Queue.Count == 0)
+                {
+                    bufferItem = null;
+
+                    return Status.NoBufferAvailaible;
+                }
+
+                if (expectedPresent != 0)
+                {
+                    // TODO: support this for advanced presenting.
+                    throw new NotImplementedException();
+                }
+
+                bufferItem = Core.Queue[0];
+
+                if (Core.StillTracking(ref bufferItem))
+                {
+                    Core.Slots[bufferItem.Slot].AcquireCalled         = true;
+                    Core.Slots[bufferItem.Slot].NeedsCleanupOnRelease = true;
+                    Core.Slots[bufferItem.Slot].BufferState           = BufferState.Acquired;
+                    Core.Slots[bufferItem.Slot].Fence                 = AndroidFence.NoFence;
+                }
+
+                if (bufferItem.AcquireCalled)
+                {
+                    bufferItem.GraphicBuffer.Reset();
+                }
+
+                Core.Queue.RemoveAt(0);
+
+                Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true));
+                Core.SignalDequeueEvent();
+            }
+
+            return Status.Success;
+        }
+
+        public Status DetachBuffer(int slot)
+        {
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.NoInit;
+                }
+
+                if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByConsumerLocked(slot))
+                {
+                    return Status.BadValue;
+                }
+
+                if (!Core.Slots[slot].RequestBufferCalled)
+                {
+                    Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer");
+
+                    return Status.BadValue;
+                }
+
+                Core.FreeBufferLocked(slot);
+                Core.SignalDequeueEvent();
+
+                return Status.Success;
+            }
+        }
+
+        public Status AttachBuffer(out int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer)
+        {
+            lock (Core.Lock)
+            {
+                int numAcquiredBuffers = 0;
+
+                int freeSlot = BufferSlotArray.InvalidBufferSlot;
+
+                for (int i = 0; i < Core.Slots.Length; i++)
+                {
+                    if (Core.Slots[i].BufferState == BufferState.Acquired)
+                    {
+                        numAcquiredBuffers++;
+                    }
+                    else if (Core.Slots[i].BufferState == BufferState.Free)
+                    {
+                        if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[i].FrameNumber < Core.Slots[freeSlot].FrameNumber)
+                        {
+                            freeSlot = i;
+                        }
+                    }
+                }
+
+                if (numAcquiredBuffers > Core.MaxAcquiredBufferCount + 1)
+                {
+                    slot = BufferSlotArray.InvalidBufferSlot;
+
+                    Logger.PrintError(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})");
+
+                    return Status.InvalidOperation;
+                }
+
+                if (freeSlot == BufferSlotArray.InvalidBufferSlot)
+                {
+                    slot = BufferSlotArray.InvalidBufferSlot;
+
+                    return Status.NoMemory;
+                }
+
+                slot = freeSlot;
+
+                Core.Slots[slot].GraphicBuffer.Set(graphicBuffer);
+
+                Core.Slots[slot].BufferState           = BufferState.Acquired;
+                Core.Slots[slot].AttachedByConsumer    = true;
+                Core.Slots[slot].NeedsCleanupOnRelease = false;
+                Core.Slots[slot].Fence                 = AndroidFence.NoFence;
+                Core.Slots[slot].FrameNumber           = 0;
+                Core.Slots[slot].AcquireCalled         = false;
+            }
+
+            return Status.Success;
+        }
+
+        public Status ReleaseBuffer(int slot, ulong frameNumber, ref AndroidFence fence)
+        {
+            if (slot < 0 || slot >= Core.Slots.Length)
+            {
+                return Status.BadValue;
+            }
+
+            IProducerListener listener = null;
+
+            lock (Core.Lock)
+            {
+                if (Core.Slots[slot].FrameNumber != frameNumber)
+                {
+                    return Status.StaleBufferSlot;
+                }
+
+                foreach (BufferItem item in Core.Queue)
+                {
+                    if (item.Slot == slot)
+                    {
+                        return Status.BadValue;
+                    }
+                }
+
+                if (Core.Slots[slot].BufferState == BufferState.Acquired)
+                {
+                    Core.Slots[slot].BufferState = BufferState.Free;
+                    Core.Slots[slot].Fence       = fence;
+
+                    listener = Core.ProducerListener;
+                }
+                else if (Core.Slots[slot].NeedsCleanupOnRelease)
+                {
+                    Core.Slots[slot].NeedsCleanupOnRelease = false;
+
+                    return Status.StaleBufferSlot;
+                }
+                else
+                {
+                    return Status.BadValue;
+                }
+
+                Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner);
+
+                Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true));
+                Core.SignalDequeueEvent();
+            }
+
+            listener?.OnBufferReleased();
+
+            return Status.Success;
+        }
+
+        public Status Connect(IConsumerListener consumerListener, bool controlledByApp)
+        {
+            if (consumerListener == null)
+            {
+                return Status.BadValue;
+            }
+
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.NoInit;
+                }
+
+                Core.ConsumerListener        = consumerListener;
+                Core.ConsumerControlledByApp = controlledByApp;
+            }
+
+            return Status.Success;
+        }
+
+        public Status Disconnect()
+        {
+            lock (Core.Lock)
+            {
+                if (!Core.IsConsumerConnectedLocked())
+                {
+                    return Status.BadValue;
+                }
+
+                Core.IsAbandoned      = true;
+                Core.ConsumerListener = null;
+
+                Core.Queue.Clear();
+                Core.FreeAllBuffersLocked();
+                Core.SignalDequeueEvent();
+            }
+
+            return Status.Success;
+        }
+
+        public Status GetReleasedBuffers(out ulong slotMask)
+        {
+            slotMask = 0;
+
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.BadValue;
+                }
+
+                for (int slot = 0; slot < Core.Slots.Length; slot++)
+                {
+                    if (!Core.Slots[slot].AcquireCalled)
+                    {
+                        slotMask |= 1UL << slot;
+                    }
+                }
+
+                for (int i = 0; i < Core.Queue.Count; i++)
+                {
+                    if (Core.Queue[i].AcquireCalled)
+                    {
+                        slotMask &= ~(1UL << i);
+                    }
+                }
+            }
+
+            return Status.Success;
+        }
+
+        public Status SetDefaultBufferSize(uint width, uint height)
+        {
+            if (width == 0 || height == 0)
+            {
+                return Status.BadValue;
+            }
+
+            lock (Core.Lock)
+            {
+                Core.DefaultWidth  = (int)width;
+                Core.DefaultHeight = (int)height;
+            }
+
+            return Status.Success;
+        }
+
+        public Status SetDefaultMaxBufferCount(int bufferMaxCount)
+        {
+            lock (Core.Lock)
+            {
+                return Core.SetDefaultMaxBufferCountLocked(bufferMaxCount);
+            }
+        }
+
+        public Status DisableAsyncBuffer()
+        {
+            lock (Core.Lock)
+            {
+                if (Core.IsConsumerConnectedLocked())
+                {
+                    return Status.InvalidOperation;
+                }
+
+                Core.UseAsyncBuffer = false;
+            }
+
+            return Status.Success;
+        }
+
+        public Status SetMaxAcquiredBufferCount(int maxAcquiredBufferCount)
+        {
+            if (maxAcquiredBufferCount < 0 || maxAcquiredBufferCount > BufferSlotArray.MaxAcquiredBuffers)
+            {
+                return Status.BadValue;
+            }
+
+            lock (Core.Lock)
+            {
+                if (Core.IsProducerConnectedLocked())
+                {
+                    return Status.InvalidOperation;
+                }
+
+                Core.MaxAcquiredBufferCount = maxAcquiredBufferCount;
+            }
+
+            return Status.Success;
+        }
+
+        public Status SetDefaultBufferFormat(PixelFormat defaultFormat)
+        {
+            lock (Core.Lock)
+            {
+                Core.DefaultBufferFormat = defaultFormat;
+            }
+
+            return Status.Success;
+        }
+
+        public Status SetConsumerUsageBits(uint usage)
+        {
+            lock (Core.Lock)
+            {
+                Core.ConsumerUsageBits = usage;
+            }
+
+            return Status.Success;
+        }
+
+        public Status SetTransformHint(NativeWindowTransform transformHint)
+        {
+            lock (Core.Lock)
+            {
+                Core.TransformHint = transformHint;
+            }
+
+            return Status.Success;
+        }
+    }
+}

+ 283 - 0
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;
+        }
+    }
+}

+ 752 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs

@@ -0,0 +1,752 @@
+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 System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class BufferQueueProducer : IGraphicBufferProducer
+    {
+        public BufferQueueCore Core { get; }
+
+        private uint _stickyTransform;
+
+        private uint _nextCallbackTicket;
+        private uint _currentCallbackTicket;
+        private uint _callbackTicket;
+
+        private readonly object _callbackLock = new object();
+
+        public BufferQueueProducer(BufferQueueCore core)
+        {
+            Core = core;
+
+            _stickyTransform       = 0;
+            _callbackTicket        = 0;
+            _nextCallbackTicket    = 0;
+            _currentCallbackTicket = 0;
+        }
+
+        public override Status RequestBuffer(int slot, out AndroidStrongPointer<GraphicBuffer> graphicBuffer)
+        {
+            graphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
+
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.NoInit;
+                }
+
+                if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
+                {
+                    return Status.BadValue;
+                }
+
+                graphicBuffer.Set(Core.Slots[slot].GraphicBuffer);
+
+                Core.Slots[slot].RequestBufferCalled = true;
+
+                return Status.Success;
+            }
+        }
+
+        public override Status SetBufferCount(int bufferCount)
+        {
+            IConsumerListener listener = null;
+
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.NoInit;
+                }
+
+                if (bufferCount > BufferSlotArray.NumBufferSlots)
+                {
+                    return Status.BadValue;
+                }
+
+                for (int slot = 0; slot < Core.Slots.Length; slot++)
+                {
+                    if (Core.Slots[slot].BufferState == BufferState.Dequeued)
+                    {
+                        return Status.BadValue;
+                    }
+                }
+
+                if (bufferCount == 0)
+                {
+                    Core.OverrideMaxBufferCount = 0;
+                    Core.SignalDequeueEvent();
+
+                    return Status.Success;
+                }
+
+                int minBufferSlots = Core.GetMinMaxBufferCountLocked(false);
+
+                if (bufferCount < minBufferSlots)
+                {
+                    return Status.BadValue;
+                }
+
+                Core.OverrideMaxBufferCount = bufferCount;
+                Core.SignalDequeueEvent();
+
+                listener = Core.ConsumerListener;
+            }
+
+            listener?.OnBuffersReleased();
+
+            return Status.Success;
+        }
+
+        public override Status DequeueBuffer(out int slot,
+                                             out AndroidFence fence,
+                                             bool async,
+                                             uint width,
+                                             uint height,
+                                             PixelFormat format,
+                                             uint usage)
+        {
+            if ((width == 0 && height != 0) || (height == 0 && width != 0))
+            {
+                slot  = BufferSlotArray.InvalidBufferSlot;
+                fence = AndroidFence.NoFence;
+
+                return Status.BadValue;
+            }
+
+            Status returnFlags = Status.Success;
+
+            bool attachedByConsumer = false;
+
+            lock (Core.Lock)
+            {
+                if (format == PixelFormat.Unknown)
+                {
+                    format = Core.DefaultBufferFormat;
+                }
+
+                usage |= Core.ConsumerUsageBits;
+
+                Status status = WaitForFreeSlotThenRelock(async, out slot, out returnFlags);
+
+                if (status != Status.Success)
+                {
+                    slot  = BufferSlotArray.InvalidBufferSlot;
+                    fence = AndroidFence.NoFence;
+
+                    return status;
+                }
+
+                if (slot == BufferSlotArray.InvalidBufferSlot)
+                {
+                    fence = AndroidFence.NoFence;
+
+                    Logger.PrintError(LogClass.SurfaceFlinger, "No available buffer slots");
+
+                    return Status.Busy;
+                }
+
+                attachedByConsumer = Core.Slots[slot].AttachedByConsumer;
+
+                if (width == 0 || height == 0)
+                {
+                    width  = (uint)Core.DefaultWidth;
+                    height = (uint)Core.DefaultHeight;
+                }
+
+                Core.Slots[slot].BufferState = BufferState.Dequeued;
+
+                GraphicBuffer graphicBuffer = Core.Slots[slot].GraphicBuffer.Object;
+
+                if (Core.Slots[slot].GraphicBuffer.IsNull
+                    || graphicBuffer.Width != width 
+                    || graphicBuffer.Height != height 
+                    || graphicBuffer.Format != format
+                    || (graphicBuffer.Usage & usage) != usage)
+                {
+                    if (Core.Slots[slot].GraphicBuffer.IsNull)
+                    {
+                        slot  = BufferSlotArray.InvalidBufferSlot;
+                        fence = AndroidFence.NoFence;
+
+                        return Status.NoMemory;
+                    }
+                    else
+                    {
+                        string formattedError = $"Preallocated buffer mismatch - slot {slot}\n" +
+                                                $"available: Width = {graphicBuffer.Width} Height = {graphicBuffer.Height} Format = {graphicBuffer.Format} Usage = {graphicBuffer.Usage:x} " +
+                                                $"requested: Width = {width} Height = {height} Format = {format} Usage = {usage:x}";
+
+                        Logger.PrintError(LogClass.SurfaceFlinger, formattedError);
+
+                        slot  = BufferSlotArray.InvalidBufferSlot;
+                        fence = AndroidFence.NoFence;
+
+                        return Status.NoInit;
+                    }
+                }
+
+                fence = Core.Slots[slot].Fence;
+
+                Core.Slots[slot].Fence = AndroidFence.NoFence;
+
+                Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(async));
+            }
+
+            if (attachedByConsumer)
+            {
+                returnFlags |= Status.BufferNeedsReallocation;
+            }
+
+            return returnFlags;
+        }
+
+        public override Status DetachBuffer(int slot)
+        {
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.NoInit;
+                }
+
+                if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
+                {
+                    return Status.BadValue;
+                }
+
+                if (!Core.Slots[slot].RequestBufferCalled)
+                {
+                    Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer");
+
+                    return Status.BadValue;
+                }
+
+                Core.FreeBufferLocked(slot);
+                Core.SignalDequeueEvent();
+
+                return Status.Success;
+            }
+        }
+
+        public override Status DetachNextBuffer(out AndroidStrongPointer<GraphicBuffer> graphicBuffer, out AndroidFence fence)
+        {
+            lock (Core.Lock)
+            {
+                Core.WaitWhileAllocatingLocked();
+
+                if (Core.IsAbandoned)
+                {
+                    graphicBuffer = default;
+                    fence         = AndroidFence.NoFence;
+
+                    return Status.NoInit;
+                }
+
+                int nextBufferSlot = BufferSlotArray.InvalidBufferSlot;
+
+                for (int slot = 0; slot < Core.Slots.Length; slot++)
+                {
+                    if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull)
+                    {
+                        if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[nextBufferSlot].FrameNumber)
+                        {
+                            nextBufferSlot = slot;
+                        }
+                    }
+                }
+
+                if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot)
+                {
+                    graphicBuffer = default;
+                    fence         = AndroidFence.NoFence;
+
+                    return Status.NoMemory;
+                }
+
+                graphicBuffer = Core.Slots[nextBufferSlot].GraphicBuffer;
+                fence         = Core.Slots[nextBufferSlot].Fence;
+
+                Core.FreeBufferLocked(nextBufferSlot);
+
+                return Status.Success;
+            }
+        }
+
+        public override Status AttachBuffer(out int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer)
+        {
+            lock (Core.Lock)
+            {
+                Status status = WaitForFreeSlotThenRelock(false, out slot, out Status returnFlags);
+
+                if (status != Status.Success)
+                {
+                    return status;
+                }
+
+                if (slot == BufferSlotArray.InvalidBufferSlot)
+                {
+                    Logger.PrintError(LogClass.SurfaceFlinger, "No available buffer slots");
+
+                    return Status.Busy;
+                }
+
+                Core.Slots[slot].GraphicBuffer.Set(graphicBuffer);
+
+                Core.Slots[slot].BufferState         = BufferState.Dequeued;
+                Core.Slots[slot].Fence               = AndroidFence.NoFence;
+                Core.Slots[slot].RequestBufferCalled = true;
+
+                return returnFlags;
+            }
+        }
+
+        public override Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output)
+        {
+            output = default;
+
+            switch (input.ScalingMode)
+            {
+                case NativeWindowScalingMode.Freeze:
+                case NativeWindowScalingMode.ScaleToWindow:
+                case NativeWindowScalingMode.ScaleCrop:
+                case NativeWindowScalingMode.Unknown:
+                case NativeWindowScalingMode.NoScaleCrop:
+                    break;
+                default:
+                    return Status.BadValue;
+            }
+
+            BufferItem item = new BufferItem();
+
+            IConsumerListener frameAvailableListener = null;
+            IConsumerListener frameReplaceListener   = null;
+
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.NoInit;
+                }
+
+                int maxBufferCount = Core.GetMaxBufferCountLocked(input.Async != 0);
+
+                if (input.Async != 0 && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount)
+                {
+                    return Status.BadValue;
+                }
+
+                if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
+                {
+                    return Status.BadValue;
+                }
+
+                if (!Core.Slots[slot].RequestBufferCalled)
+                {
+                    Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} was queued without requesting a buffer");
+
+                    return Status.BadValue;
+                }
+
+                input.Crop.Intersect(Core.Slots[slot].GraphicBuffer.Object.ToRect(), out Rect croppedRect);
+
+                if (croppedRect != input.Crop)
+                {
+                    return Status.BadValue;
+                }
+
+                Core.Slots[slot].Fence       = input.Fence;
+                Core.Slots[slot].BufferState = BufferState.Queued;
+                Core.FrameCounter++;
+                Core.Slots[slot].FrameNumber = Core.FrameCounter;
+
+                item.AcquireCalled             = Core.Slots[slot].AcquireCalled;
+                item.Crop                      = input.Crop;
+                item.Transform                 = input.Transform;
+                item.TransformToDisplayInverse = (input.Transform & NativeWindowTransform.InverseDisplay) == NativeWindowTransform.InverseDisplay;
+                item.ScalingMode               = input.ScalingMode;
+                item.Timestamp                 = input.Timestamp;
+                item.IsAutoTimestamp           = input.IsAutoTimestamp != 0;
+                item.SwapInterval              = input.SwapInterval;
+                item.FrameNumber               = Core.FrameCounter;
+                item.Slot                      = slot;
+                item.Fence                     = input.Fence;
+                item.IsDroppable               = Core.DequeueBufferCannotBlock || input.Async != 0;
+
+                item.GraphicBuffer.Set(Core.Slots[slot].GraphicBuffer);
+                item.GraphicBuffer.Object.IncrementNvMapHandleRefCount(Core.Owner);
+
+                _stickyTransform = input.StickyTransform;
+
+                if (Core.Queue.Count == 0)
+                {
+                    Core.Queue.Add(item);
+
+                    frameAvailableListener = Core.ConsumerListener;
+                }
+                else
+                {
+                    BufferItem frontItem = Core.Queue[0];
+
+                    if (frontItem.IsDroppable)
+                    {
+                        if (Core.StillTracking(ref frontItem))
+                        {
+                            Core.Slots[slot].BufferState = BufferState.Free;
+                            Core.Slots[slot].FrameNumber = 0;
+                        }
+
+                        Core.Queue.RemoveAt(0);
+                        Core.Queue.Insert(0, item);
+
+                        frameReplaceListener = Core.ConsumerListener;
+                    }
+                    else
+                    {
+                        Core.Queue.Add(item);
+
+                        frameAvailableListener = Core.ConsumerListener;
+                    }
+                }
+
+                Core.BufferHasBeenQueued = true;
+                Core.SignalDequeueEvent();
+
+                Core.CheckSystemEventsLocked(maxBufferCount);
+
+                output = new QueueBufferOutput
+                {
+                    Width             = (uint)Core.DefaultWidth,
+                    Height            = (uint)Core.DefaultHeight,
+                    TransformHint     = Core.TransformHint,
+                    NumPendingBuffers = (uint)Core.Queue.Count
+                };
+
+                _callbackTicket = _nextCallbackTicket++;
+            }
+
+            lock (_callbackLock)
+            {
+                while (_callbackTicket != _currentCallbackTicket)
+                {
+                    Monitor.Wait(_callbackLock);
+                }
+
+                frameAvailableListener?.OnFrameAvailable(ref item);
+                frameReplaceListener?.OnFrameReplaced(ref item);
+
+                _currentCallbackTicket++;
+
+                Monitor.PulseAll(_callbackLock);
+            }
+
+            return Status.Success;
+        }
+
+        public override void CancelBuffer(int slot, ref AndroidFence fence)
+        {
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned || slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
+                {
+                    return;
+                }
+
+                Core.Slots[slot].BufferState = BufferState.Free;
+                Core.Slots[slot].FrameNumber = 0;
+                Core.Slots[slot].Fence       = fence;
+                Core.SignalDequeueEvent();
+            }
+        }
+
+        public override Status Query(NativeWindowAttribute what, out int outValue)
+        {
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    outValue = 0;
+                    return Status.NoInit;
+                }
+
+                switch (what)
+                {
+                    case NativeWindowAttribute.Width:
+                        outValue = Core.DefaultWidth;
+                        return Status.Success;
+                    case NativeWindowAttribute.Height:
+                        outValue = Core.DefaultHeight;
+                        return Status.Success;
+                    case NativeWindowAttribute.Format:
+                        outValue = (int)Core.DefaultBufferFormat;
+                        return Status.Success;
+                    case NativeWindowAttribute.MinUnqueuedBuffers:
+                        outValue = Core.GetMinUndequeuedBufferCountLocked(false);
+                        return Status.Success;
+                    case NativeWindowAttribute.ConsumerUsageBits:
+                        outValue = (int)Core.ConsumerUsageBits;
+                        return Status.Success;
+                    case NativeWindowAttribute.MaxBufferCountAsync:
+                        outValue = Core.GetMaxBufferCountLocked(true);
+                        return Status.Success;
+                    default:
+                        outValue = 0;
+                        return Status.BadValue;
+                }
+            }
+        }
+
+        public override Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output)
+        {
+            output = new QueueBufferOutput();
+
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned || Core.ConsumerListener == null)
+                {
+                    return Status.NoInit;
+                }
+
+                if (Core.ConnectedApi != NativeWindowApi.NoApi)
+                {
+                    return Status.BadValue;
+                }
+
+                Core.BufferHasBeenQueued      = false;
+                Core.DequeueBufferCannotBlock = Core.ConsumerControlledByApp && producerControlledByApp;
+
+                switch (api)
+                {
+                    case NativeWindowApi.NVN:
+                    case NativeWindowApi.CPU:
+                    case NativeWindowApi.Media:
+                    case NativeWindowApi.Camera:
+                        Core.ProducerListener = listener;
+                        Core.ConnectedApi     = api;
+
+                        output.Width             = (uint)Core.DefaultWidth;
+                        output.Height            = (uint)Core.DefaultHeight;
+                        output.TransformHint     = Core.TransformHint;
+                        output.NumPendingBuffers = (uint)Core.Queue.Count;
+                        return Status.Success;
+                    default:
+                        return Status.BadValue;
+                }
+            }
+        }
+
+        public override Status Disconnect(NativeWindowApi api)
+        {
+            IProducerListener producerListener = null;
+
+            Status status = Status.BadValue;
+
+            lock (Core.Lock)
+            {
+                if (Core.IsAbandoned)
+                {
+                    return Status.Success;
+                }
+
+                switch (api)
+                {
+                    case NativeWindowApi.NVN:
+                    case NativeWindowApi.CPU:
+                    case NativeWindowApi.Media:
+                    case NativeWindowApi.Camera:
+                        if (Core.ConnectedApi == api)
+                        {
+                            Core.FreeAllBuffersLocked();
+
+                            producerListener = Core.ProducerListener;
+
+                            Core.ProducerListener = null;
+                            Core.ConnectedApi     = NativeWindowApi.NoApi;
+
+                            Core.SignalDequeueEvent();
+
+                            status = Status.Success;
+                        }
+                        break;
+                }
+            }
+
+            producerListener?.OnBufferReleased();
+
+            return status;
+        }
+
+        public override Status SetPreallocatedBuffer(int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer)
+        {
+            if (slot < 0 || slot >= Core.Slots.Length)
+            {
+                return Status.BadValue;
+            }
+
+            lock (Core.Lock)
+            {
+                Core.Slots[slot].BufferState           = BufferState.Free;
+                Core.Slots[slot].Fence                 = AndroidFence.NoFence;
+                Core.Slots[slot].RequestBufferCalled   = false;
+                Core.Slots[slot].NeedsCleanupOnRelease = false;
+                Core.Slots[slot].FrameNumber           = 0;
+
+                Core.Slots[slot].GraphicBuffer.Set(graphicBuffer);
+
+                if (!Core.Slots[slot].GraphicBuffer.IsNull)
+                {
+                    Core.Slots[slot].GraphicBuffer.Object.Buffer.Usage &= (int)Core.ConsumerUsageBits;
+                }
+
+                bool cleared = false;
+
+                if (!graphicBuffer.IsNull)
+                {
+                    // NOTE: Nintendo set the default width, height and format from the GraphicBuffer..
+                    //       This is entirely wrong and should only be controlled by the consumer...
+                    Core.DefaultWidth        = graphicBuffer.Object.Width;
+                    Core.DefaultHeight       = graphicBuffer.Object.Height;
+                    Core.DefaultBufferFormat = graphicBuffer.Object.Format;
+                }
+                else
+                {
+                    foreach (BufferItem item in Core.Queue)
+                    {
+                        if (item.Slot >= BufferSlotArray.NumBufferSlots)
+                        {
+                            Core.Queue.Clear();
+                            Core.FreeAllBuffersLocked();
+                            Core.SignalDequeueEvent();
+                            Core.SignalWaitBufferFreeEvent();
+                            Core.SignalFrameAvailableEvent();
+
+                            cleared = true;
+
+                            break;
+                        }
+                    }
+                }
+
+                // The dequeue event must not be signaled two times in case of clean up,
+                // but for some reason, it still signals the wait buffer free event two times...
+                if (!cleared)
+                {
+                    Core.SignalDequeueEvent();
+                }
+
+                Core.SignalWaitBufferFreeEvent();
+
+                return Status.Success;
+            }
+        }
+
+        private Status WaitForFreeSlotThenRelock(bool async, out int freeSlot, out Status returnStatus)
+        {
+            bool tryAgain = true;
+
+            freeSlot     = BufferSlotArray.InvalidBufferSlot;
+            returnStatus = Status.Success;
+
+            while (tryAgain)
+            {
+                if (Core.IsAbandoned)
+                {
+                    freeSlot = BufferSlotArray.InvalidBufferSlot;
+
+                    return Status.NoInit;
+                }
+
+                int maxBufferCount = Core.GetMaxBufferCountLocked(async);
+
+                if (async && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount)
+                {
+                    freeSlot = BufferSlotArray.InvalidBufferSlot;
+
+                    return Status.BadValue;
+                }
+
+                for (int slot = maxBufferCount; slot < Core.Slots.Length; slot++)
+                {
+                    if (!Core.Slots[slot].GraphicBuffer.IsNull)
+                    {
+                        Core.FreeBufferLocked(slot);
+                        returnStatus |= Status.ReleaseAllBuffers;
+                    }
+                }
+
+                freeSlot = BufferSlotArray.InvalidBufferSlot;
+
+                int dequeuedCount = 0;
+                int acquiredCount = 0;
+
+                for (int slot = 0; slot < maxBufferCount; slot++)
+                {
+                    switch (Core.Slots[slot].BufferState)
+                    {
+                        case BufferState.Acquired:
+                            acquiredCount++;
+                            break;
+                        case BufferState.Dequeued:
+                            dequeuedCount++;
+                            break;
+                        case BufferState.Free:
+                            if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[freeSlot].FrameNumber)
+                            {
+                                freeSlot = slot;
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                }
+
+                // The producer SHOULD call SetBufferCount otherwise it's not allowed to dequeue multiple buffers.
+                if (Core.OverrideMaxBufferCount == 0 && dequeuedCount > 0)
+                {
+                    return Status.InvalidOperation;
+                }
+
+                if (Core.BufferHasBeenQueued)
+                {
+                    int newUndequeuedCount = maxBufferCount - (dequeuedCount + 1);
+                    int minUndequeuedCount = Core.GetMinUndequeuedBufferCountLocked(async);
+
+                    if (newUndequeuedCount < minUndequeuedCount)
+                    {
+                        Logger.PrintError(LogClass.SurfaceFlinger, $"Min undequeued buffer count ({minUndequeuedCount}) exceeded (dequeued = {dequeuedCount} undequeued = {newUndequeuedCount})");
+
+                        return Status.InvalidOperation;
+                    }
+                }
+
+                bool tooManyBuffers = Core.Queue.Count > maxBufferCount;
+
+                tryAgain = freeSlot == BufferSlotArray.InvalidBufferSlot || tooManyBuffers;
+
+                if (tryAgain)
+                {
+                    if (async || (Core.DequeueBufferCannotBlock && acquiredCount < Core.MaxAcquiredBufferCount))
+                    {
+                        Core.CheckSystemEventsLocked(maxBufferCount);
+
+                        return Status.WouldBlock;
+                    }
+
+                    Core.WaitDequeueEvent();
+                }
+            }
+
+            return Status.Success;
+        }
+
+        protected override KReadableEvent GetWaitBufferFreeEvent()
+        {
+            return Core.GetWaitBufferFreeEvent();
+        }
+    }
+}

+ 22 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs

@@ -0,0 +1,22 @@
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class BufferSlot
+    {
+        public AndroidStrongPointer<GraphicBuffer> GraphicBuffer;
+        public BufferState                         BufferState;
+        public bool                                RequestBufferCalled;
+        public ulong                               FrameNumber;
+        public AndroidFence                        Fence;
+        public bool                                AcquireCalled;
+        public bool                                NeedsCleanupOnRelease;
+        public bool                                AttachedByConsumer;
+
+        public BufferSlot()
+        {
+            GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
+            BufferState   = BufferState.Free;
+        }
+    }
+}

+ 28 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs

@@ -0,0 +1,28 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class BufferSlotArray
+    {
+        // TODO: move to BufferQueue
+        public const int NumBufferSlots     = 0x40;
+        public const int MaxAcquiredBuffers = NumBufferSlots - 2;
+        public const int InvalidBufferSlot  = -1;
+
+        private BufferSlot[] _raw = new BufferSlot[NumBufferSlots];
+
+        public BufferSlotArray()
+        {
+            for (int i = 0; i < _raw.Length; i++)
+            {
+                _raw[i] = new BufferSlot();
+            }
+        }
+
+        public BufferSlot this[int index]
+        {
+            get => _raw[index];
+            set => _raw[index] = value;
+        }
+
+        public int Length => NumBufferSlots;
+    }
+}

+ 175 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs

@@ -0,0 +1,175 @@
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class ConsumerBase : IConsumerListener
+    {
+        public class Slot
+        {
+            public AndroidStrongPointer<GraphicBuffer> GraphicBuffer;
+            public AndroidFence                        Fence;
+            public ulong                               FrameNumber;
+
+            public Slot()
+            {
+                GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
+            }
+        }
+
+        protected Slot[] Slots = new Slot[BufferSlotArray.NumBufferSlots];
+
+        protected bool IsAbandoned;
+
+        protected BufferQueueConsumer Consumer;
+
+        protected readonly object Lock = new object();
+
+        private IConsumerListener _listener;
+
+        public ConsumerBase(BufferQueueConsumer consumer, bool controlledByApp, IConsumerListener listener)
+        {
+            for (int i = 0; i < Slots.Length; i++)
+            {
+                Slots[i] = new Slot();
+            }
+
+            IsAbandoned = false;
+            Consumer    = consumer;
+            _listener   = listener;
+
+            Status connectStatus = consumer.Connect(this, controlledByApp);
+
+            if (connectStatus != Status.Success)
+            {
+                throw new InvalidOperationException();
+            }
+        }
+
+        public virtual void OnBuffersReleased()
+        {
+            lock (Lock)
+            {
+                if (IsAbandoned)
+                {
+                    return;
+                }
+
+                Consumer.GetReleasedBuffers(out ulong slotMask);
+
+                for (int i = 0; i < Slots.Length; i++)
+                {
+                    if ((slotMask & (1UL << i)) != 0)
+                    {
+                        FreeBufferLocked(i);
+                    }
+                }
+            }
+        }
+
+        public virtual void OnFrameAvailable(ref BufferItem item)
+        {
+            _listener?.OnFrameAvailable(ref item);
+        }
+
+        public virtual void OnFrameReplaced(ref BufferItem item)
+        {
+            _listener?.OnFrameReplaced(ref item);
+        }
+
+        protected virtual void FreeBufferLocked(int slotIndex)
+        {
+            Slots[slotIndex].GraphicBuffer.Reset();
+
+            Slots[slotIndex].Fence       = AndroidFence.NoFence;
+            Slots[slotIndex].FrameNumber = 0;
+        }
+
+        public void Abandon()
+        {
+            lock (Lock)
+            {
+                if (!IsAbandoned)
+                {
+                    AbandonLocked();
+
+                    IsAbandoned = true;
+                }
+            }
+        }
+
+        protected virtual void AbandonLocked()
+        {
+            for (int i = 0; i < Slots.Length; i++)
+            {
+                FreeBufferLocked(i);
+            }
+
+            Consumer.Disconnect();
+        }
+
+        protected virtual Status AcquireBufferLocked(out BufferItem bufferItem, ulong expectedPresent)
+        {
+            Status acquireStatus = Consumer.AcquireBuffer(out bufferItem, expectedPresent);
+
+            if (acquireStatus != Status.Success)
+            {
+                return acquireStatus;
+            }
+
+            if (!bufferItem.GraphicBuffer.IsNull)
+            {
+                Slots[bufferItem.Slot].GraphicBuffer.Set(bufferItem.GraphicBuffer.Object);
+            }
+
+            Slots[bufferItem.Slot].FrameNumber = bufferItem.FrameNumber;
+            Slots[bufferItem.Slot].Fence       = bufferItem.Fence;
+
+            return Status.Success;
+        }
+
+        protected virtual Status AddReleaseFenceLocked(int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer, ref AndroidFence fence)
+        {
+            if (!StillTracking(slot, ref graphicBuffer))
+            {
+                return Status.Success;
+            }
+
+            Slots[slot].Fence = fence;
+
+            return Status.Success;
+        }
+
+        protected virtual Status ReleaseBufferLocked(int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer)
+        {
+            if (!StillTracking(slot, ref graphicBuffer))
+            {
+                return Status.Success;
+            }
+
+            Status result = Consumer.ReleaseBuffer(slot, Slots[slot].FrameNumber, ref Slots[slot].Fence);
+
+            if (result == Status.StaleBufferSlot)
+            {
+                FreeBufferLocked(slot);
+            }
+
+            Slots[slot].Fence = AndroidFence.NoFence;
+
+            return result;
+        }
+
+        protected virtual bool StillTracking(int slotIndex, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer)
+        {
+            if (slotIndex < 0 || slotIndex >= Slots.Length)
+            {
+                return false;
+            }
+
+            Slot slot = Slots[slotIndex];
+
+            // 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 == graphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
+        }
+    }
+}

+ 109 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs

@@ -0,0 +1,109 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class HOSBinderDriverServer : IHOSBinderDriver
+    {
+        private static Dictionary<int, IBinder> _registeredBinderObjects = new Dictionary<int, IBinder>();
+
+        private static int _lastBinderId = 0;
+
+        private static object _lock = new object();
+
+        public static int RegisterBinderObject(IBinder binder)
+        {
+            lock (_lock)
+            {
+                _lastBinderId++;
+
+                _registeredBinderObjects.Add(_lastBinderId, binder);
+
+                return _lastBinderId;
+            }
+        }
+
+        public static void UnregisterBinderObject(int binderId)
+        {
+            lock (_lock)
+            {
+                _registeredBinderObjects.Remove(binderId);
+            }
+        }
+
+        public static int GetBinderId(IBinder binder)
+        {
+            lock (_lock)
+            {
+                foreach (KeyValuePair<int, IBinder> pair in _registeredBinderObjects)
+                {
+                    if (ReferenceEquals(binder, pair.Value))
+                    {
+                        return pair.Key;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        private static IBinder GetBinderObjectById(int binderId)
+        {
+            lock (_lock)
+            {
+                if (_registeredBinderObjects.TryGetValue(binderId, out IBinder binder))
+                {
+                    return binder;
+                }
+
+                return null;
+            }
+        }
+
+        protected override ResultCode AdjustRefcount(int binderId, int addVal, int type)
+        {
+            IBinder binder = GetBinderObjectById(binderId);
+
+            if (binder == null)
+            {
+                Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}");
+
+                return ResultCode.Success;
+            }
+
+            return binder.AdjustRefcount(addVal, type);
+        }
+
+        protected override void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent)
+        {
+            IBinder binder = GetBinderObjectById(binderId);
+
+            if (binder == null)
+            {
+                readableEvent = null;
+
+                Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}");
+
+                return;
+            }
+
+            binder.GetNativeHandle(typeId, out readableEvent);
+        }
+
+        protected override ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel)
+        {
+            IBinder binder = GetBinderObjectById(binderId);
+
+            if (binder == null)
+            {
+                Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}");
+
+                return ResultCode.Success;
+            }
+
+            return binder.OnTransact(code, flags, inputParcel, outputParcel);
+        }
+    }
+}

+ 41 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs

@@ -0,0 +1,41 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    interface IBinder
+    {
+        ResultCode AdjustRefcount(int addVal, int type);
+
+        void GetNativeHandle(uint typeId, out KReadableEvent readableEvent);
+
+        ResultCode OnTransact(uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel)
+        {
+            Parcel inputParcelReader = new Parcel(inputParcel.ToArray());
+
+            // TODO: support objects?
+            Parcel outputParcelWriter = new Parcel((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0);
+
+            string inputInterfaceToken = inputParcelReader.ReadInterfaceToken();
+
+            if (!InterfaceToken.Equals(inputInterfaceToken))
+            {
+                Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid interface token {inputInterfaceToken} (expected: {InterfaceToken}");
+
+                return ResultCode.Success;
+            }
+
+            OnTransact(code, flags, inputParcelReader, outputParcelWriter);
+
+            outputParcelWriter.Finish().CopyTo(outputParcel);
+
+            return ResultCode.Success;
+        }
+
+        void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel);
+
+        string InterfaceToken { get; }
+    }
+}

+ 9 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    interface IConsumerListener
+    {
+        void OnFrameAvailable(ref BufferItem item);
+        void OnFrameReplaced(ref BufferItem item);
+        void OnBuffersReleased();
+    }
+}

+ 13 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs

@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    interface IFlattenable
+    {
+        uint GetFlattenedSize();
+
+        uint GetFdCount();
+
+        void Flatten(Parcel parcel);
+
+        void Unflatten(Parcel parcel);
+    }
+}

+ 276 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs

@@ -0,0 +1,276 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    abstract class IGraphicBufferProducer : IBinder
+    {
+        public string InterfaceToken => "android.gui.IGraphicBufferProducer";
+
+        enum TransactionCode : uint
+        {
+            RequestBuffer = 1,
+            SetBufferCount,
+            DequeueBuffer,
+            DetachBuffer,
+            DetachNextBuffer,
+            AttachBuffer,
+            QueueBuffer,
+            CancelBuffer,
+            Query,
+            Connect,
+            Disconnect,
+            SetSidebandStream,
+            AllocateBuffers,
+            SetPreallocatedBuffer,
+            Reserved15,
+            GetBufferInfo,
+            GetBufferHistory
+        }
+
+        [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x54)]
+        public struct QueueBufferInput : IFlattenable
+        {
+            public long                    Timestamp;
+            public int                     IsAutoTimestamp;
+            public Rect                    Crop;
+            public NativeWindowScalingMode ScalingMode;
+            public NativeWindowTransform   Transform;
+            public uint                    StickyTransform;
+            public int                     Async;
+            public int                     SwapInterval;
+            public AndroidFence            Fence;
+
+            public void Flatten(Parcel parcel)
+            {
+                parcel.WriteUnmanagedType(ref this);
+            }
+
+            public uint GetFdCount()
+            {
+                return 0;
+            }
+
+            public uint GetFlattenedSize()
+            {
+                return (uint)Unsafe.SizeOf<QueueBufferInput>();
+            }
+
+            public void Unflatten(Parcel parcel)
+            {
+                this = parcel.ReadUnmanagedType<QueueBufferInput>();
+            }
+        }
+
+        public struct QueueBufferOutput
+        {
+            public uint                  Width;
+            public uint                  Height;
+            public NativeWindowTransform TransformHint;
+            public uint                  NumPendingBuffers;
+        }
+
+        public ResultCode AdjustRefcount(int addVal, int type)
+        {
+            // TODO?
+            return ResultCode.Success;
+        }
+
+        public void GetNativeHandle(uint typeId, out KReadableEvent readableEvent)
+        {
+            if (typeId == 0xF)
+            {
+                readableEvent = GetWaitBufferFreeEvent();
+            }
+            else
+            {
+                throw new NotImplementedException($"Unimplemented native event type {typeId}!");
+            }
+        }
+
+        public void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel)
+        {
+            Status            status = Status.Success;
+            int               slot;
+            AndroidFence      fence;
+            QueueBufferInput  queueInput;
+            QueueBufferOutput queueOutput;
+            NativeWindowApi   api;
+
+            AndroidStrongPointer<GraphicBuffer> graphicBuffer;
+            AndroidStrongPointer<AndroidFence>  strongFence;
+
+            switch ((TransactionCode)code)
+            {
+                case TransactionCode.RequestBuffer:
+                    slot = inputParcel.ReadInt32();
+
+                    status = RequestBuffer(slot, out graphicBuffer);
+
+                    outputParcel.WriteStrongPointer(ref graphicBuffer);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.SetBufferCount:
+                    int bufferCount = inputParcel.ReadInt32();
+
+                    status = SetBufferCount(bufferCount);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.DequeueBuffer:
+                    bool        async  = inputParcel.ReadBoolean();
+                    uint        width  = inputParcel.ReadUInt32();
+                    uint        height = inputParcel.ReadUInt32();
+                    PixelFormat format = inputParcel.ReadUnmanagedType<PixelFormat>();
+                    uint        usage  = inputParcel.ReadUInt32();
+
+                    status      = DequeueBuffer(out int dequeueSlot, out fence, async, width, height, format, usage);
+                    strongFence = new AndroidStrongPointer<AndroidFence>(fence);
+
+                    outputParcel.WriteInt32(dequeueSlot);
+                    outputParcel.WriteStrongPointer(ref strongFence);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.DetachBuffer:
+                    slot = inputParcel.ReadInt32();
+
+                    status = DetachBuffer(slot);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.DetachNextBuffer:
+                    status      = DetachNextBuffer(out graphicBuffer, out fence);
+                    strongFence = new AndroidStrongPointer<AndroidFence>(fence);
+
+                    outputParcel.WriteStrongPointer(ref graphicBuffer);
+                    outputParcel.WriteStrongPointer(ref strongFence);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.AttachBuffer:
+                    graphicBuffer = inputParcel.ReadStrongPointer<GraphicBuffer>();
+
+                    status = AttachBuffer(out slot, graphicBuffer);
+
+                    outputParcel.WriteInt32(slot);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.QueueBuffer:
+                    slot       = inputParcel.ReadInt32();
+                    queueInput = inputParcel.ReadFlattenable<QueueBufferInput>();
+
+                    status = QueueBuffer(slot, ref queueInput, out queueOutput);
+
+                    outputParcel.WriteUnmanagedType(ref queueOutput);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.CancelBuffer:
+                    slot  = inputParcel.ReadInt32();
+                    fence = inputParcel.ReadFlattenable<AndroidFence>();
+
+                    CancelBuffer(slot, ref fence);
+
+                    outputParcel.WriteStatus(Status.Success);
+
+                    break;
+                case TransactionCode.Query:
+                    NativeWindowAttribute what = inputParcel.ReadUnmanagedType<NativeWindowAttribute>();
+
+                    status = Query(what, out int outValue);
+
+                    outputParcel.WriteInt32(outValue);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.Connect:
+                    bool hasListener = inputParcel.ReadBoolean();
+
+                    IProducerListener listener = null;
+
+                    if (hasListener)
+                    {
+                        throw new NotImplementedException("Connect with a strong binder listener isn't implemented");
+                    }
+
+                    api = inputParcel.ReadUnmanagedType<NativeWindowApi>();
+
+                    bool producerControlledByApp = inputParcel.ReadBoolean();
+
+                    status = Connect(listener, api, producerControlledByApp, out queueOutput);
+
+                    outputParcel.WriteUnmanagedType(ref queueOutput);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.Disconnect:
+                    api = inputParcel.ReadUnmanagedType<NativeWindowApi>();
+
+                    status = Disconnect(api);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                case TransactionCode.SetPreallocatedBuffer:
+                    slot = inputParcel.ReadInt32();
+
+                    graphicBuffer = inputParcel.ReadStrongPointer<GraphicBuffer>();
+
+                    status = SetPreallocatedBuffer(slot, graphicBuffer);
+
+                    outputParcel.WriteStatus(status);
+
+                    break;
+                default:
+                    throw new NotImplementedException($"Transaction {(TransactionCode)code} not implemented");
+            }
+
+            if (status != Status.Success)
+            {
+                Logger.PrintError(LogClass.SurfaceFlinger, $"Error returned by transaction {(TransactionCode)code}: {status}");
+            }
+        }
+
+        protected abstract KReadableEvent GetWaitBufferFreeEvent();
+
+        public abstract Status RequestBuffer(int slot, out AndroidStrongPointer<GraphicBuffer> graphicBuffer);
+
+        public abstract Status SetBufferCount(int bufferCount);
+
+        public abstract Status DequeueBuffer(out int slot, out AndroidFence fence, bool async, uint width, uint height, PixelFormat format, uint usage);
+
+        public abstract Status DetachBuffer(int slot);
+
+        public abstract Status DetachNextBuffer(out AndroidStrongPointer<GraphicBuffer> graphicBuffer, out AndroidFence fence);
+
+        public abstract Status AttachBuffer(out int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer);
+
+        public abstract Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output);
+
+        public abstract void CancelBuffer(int slot, ref AndroidFence fence);
+
+        public abstract Status Query(NativeWindowAttribute what, out int outValue);
+
+        public abstract Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output);
+
+        public abstract Status Disconnect(NativeWindowApi api);
+
+        public abstract Status SetPreallocatedBuffer(int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer);
+    }
+}

+ 104 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs

@@ -0,0 +1,104 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    abstract class IHOSBinderDriver : IpcService
+    {
+        public IHOSBinderDriver() {}
+
+        [Command(0)]
+        // TransactParcel(s32, u32, u32, buffer<unknown, 5, 0>) -> buffer<unknown, 6, 0>
+        public ResultCode TransactParcel(ServiceCtx context)
+        {
+            int binderId = context.RequestData.ReadInt32();
+
+            uint code  = context.RequestData.ReadUInt32();
+            uint flags = context.RequestData.ReadUInt32();
+
+            ulong dataPos  = (ulong)context.Request.SendBuff[0].Position;
+            ulong dataSize = (ulong)context.Request.SendBuff[0].Size;
+
+            long replyPos  = context.Request.ReceiveBuff[0].Position;
+            long replySize = context.Request.ReceiveBuff[0].Size;
+
+            ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, dataSize);
+
+            Span<byte> outputParcel = new Span<byte>(new byte[replySize]);
+
+            ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
+
+            if (result == ResultCode.Success)
+            {
+                context.Memory.WriteBytes(replyPos, outputParcel.ToArray());
+            }
+
+            return result;
+        }
+
+        [Command(1)]
+        // AdjustRefcount(s32, s32, s32)
+        public ResultCode AdjustRefcount(ServiceCtx context)
+        {
+            int binderId = context.RequestData.ReadInt32();
+            int addVal   = context.RequestData.ReadInt32();
+            int type     = context.RequestData.ReadInt32();
+
+            return AdjustRefcount(binderId, addVal, type);
+        }
+
+        [Command(2)]
+        // GetNativeHandle(s32, s32) -> handle<copy>
+        public ResultCode GetNativeHandle(ServiceCtx context)
+        {
+            int binderId = context.RequestData.ReadInt32();
+
+            uint typeId = context.RequestData.ReadUInt32();
+
+            GetNativeHandle(binderId, typeId, out KReadableEvent readableEvent);
+
+            if (context.Process.HandleTable.GenerateHandle(readableEvent, out int handle) != KernelResult.Success)
+            {
+                throw new InvalidOperationException("Out of handles!");
+            }
+
+            context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
+
+            return ResultCode.Success;
+        }
+
+        [Command(3)] // 3.0.0+
+        // TransactParcelAuto(s32, u32, u32, buffer<unknown, 21, 0>) -> buffer<unknown, 22, 0>
+        public ResultCode TransactParcelAuto(ServiceCtx context)
+        {
+            int binderId = context.RequestData.ReadInt32();
+            
+            uint code  = context.RequestData.ReadUInt32();
+            uint flags = context.RequestData.ReadUInt32();
+
+            (long dataPos, long dataSize)   = context.Request.GetBufferType0x21();
+            (long replyPos, long replySize) = context.Request.GetBufferType0x22();
+
+            ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan((ulong)dataPos, (ulong)dataSize);
+
+            Span<byte> outputParcel = new Span<byte>(new byte[replySize]);
+
+            ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
+
+            if (result == ResultCode.Success)
+            {
+                context.Memory.WriteBytes(replyPos, outputParcel.ToArray());
+            }
+
+            return result;
+        }
+
+        protected abstract ResultCode AdjustRefcount(int binderId, int addVal, int type);
+
+        protected abstract void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent);
+
+        protected abstract ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel);
+    }
+}

+ 7 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs

@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    interface IProducerListener
+    {
+        void OnBufferReleased();
+    }
+}

+ 11 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs

@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    enum NativeWindowApi : int
+    {
+        NoApi  = 0,
+        NVN    = 1,
+        CPU    = 2,
+        Media  = 3,
+        Camera = 4
+    }
+}

+ 13 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs

@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    enum NativeWindowAttribute : uint
+    {
+        Width                 = 0,
+        Height                = 1,
+        Format                = 2,
+        MinUnqueuedBuffers    = 3,
+        ConsumerRunningBehind = 9,
+        ConsumerUsageBits     = 10,
+        MaxBufferCountAsync   = 12
+    }
+}

+ 11 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs

@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    enum NativeWindowScalingMode : uint
+    {
+        Freeze        = 0,
+        ScaleToWindow = 1,
+        ScaleCrop     = 2,
+        Unknown       = 3,
+        NoScaleCrop   = 4,
+    }
+}

+ 16 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    [Flags]
+    enum NativeWindowTransform : uint
+    {
+        None           = 0,
+        FlipX          = 1,
+        FlipY          = 2,
+        Rotate90       = 4,
+        Rotate180      = FlipX | FlipY,
+        Rotate270      = Rotate90 | Rotate180,
+        InverseDisplay = 8
+    }
+}

+ 0 - 455
Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs

@@ -1,455 +0,0 @@
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Gpu;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading;
-
-using static Ryujinx.HLE.HOS.Services.SurfaceFlinger.Parcel;
-
-namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
-{
-    class NvFlinger : IDisposable
-    {
-        private delegate ResultCode ServiceProcessParcel(ServiceCtx context, BinaryReader parcelReader);
-
-        private Dictionary<(string, int), ServiceProcessParcel> _commands;
-
-        private KEvent _binderEvent;
-
-        private IRenderer _renderer;
-
-        private const int BufferQueueCount = 0x40;
-        private const int BufferQueueMask  = BufferQueueCount - 1;
-
-        private BufferEntry[] _bufferQueue;
-
-        private AutoResetEvent _waitBufferFree;
-
-        private bool _disposed;
-
-        public NvFlinger(IRenderer renderer, KEvent binderEvent)
-        {
-            _commands = new Dictionary<(string, int), ServiceProcessParcel>
-            {
-                { ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer  },
-                { ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer  },
-                { ("android.gui.IGraphicBufferProducer", 0x4), GbpDetachBuffer   },
-                { ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer    },
-                { ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer   },
-                { ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery          },
-                { ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect        },
-                { ("android.gui.IGraphicBufferProducer", 0xb), GbpDisconnect     },
-                { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer }
-            };
-
-            _renderer    = renderer;
-            _binderEvent = binderEvent;
-
-            _bufferQueue = new BufferEntry[0x40];
-
-            _waitBufferFree = new AutoResetEvent(false);
-        }
-
-        public ResultCode ProcessParcelRequest(ServiceCtx context, byte[] parcelData, int code)
-        {
-            using (MemoryStream ms = new MemoryStream(parcelData))
-            {
-                BinaryReader reader = new BinaryReader(ms);
-
-                ms.Seek(4, SeekOrigin.Current);
-
-                int strSize = reader.ReadInt32();
-
-                string interfaceName = Encoding.Unicode.GetString(reader.ReadBytes(strSize * 2));
-
-                long remainder = ms.Position & 0xf;
-
-                if (remainder != 0)
-                {
-                    ms.Seek(0x10 - remainder, SeekOrigin.Current);
-                }
-
-                ms.Seek(0x50, SeekOrigin.Begin);
-
-                if (_commands.TryGetValue((interfaceName, code), out ServiceProcessParcel procReq))
-                {
-                    Logger.PrintDebug(LogClass.ServiceVi, $"{interfaceName} {procReq.Method.Name}");
-
-                    return procReq(context, reader);
-                }
-                else
-                {
-                    throw new NotImplementedException($"{interfaceName} {code}");
-                }
-            }
-        }
-
-        private ResultCode GbpRequestBuffer(ServiceCtx context, BinaryReader parcelReader)
-        {
-            int slot = parcelReader.ReadInt32();
-
-            using (MemoryStream ms = new MemoryStream())
-            {
-                BinaryWriter writer = new BinaryWriter(ms);
-
-                BufferEntry entry = _bufferQueue[slot];
-
-                int  bufferCount = 1; //?
-                long bufferSize  = entry.Data.Size;
-
-                writer.Write(bufferCount);
-                writer.Write(bufferSize);
-
-                entry.Data.Write(writer);
-
-                writer.Write(0);
-
-                return MakeReplyParcel(context, ms.ToArray());
-            }
-        }
-
-        private ResultCode GbpDequeueBuffer(ServiceCtx context, BinaryReader parcelReader)
-        {
-            // TODO: Errors.
-            int async  = parcelReader.ReadInt32();
-            int width  = parcelReader.ReadInt32();
-            int height = parcelReader.ReadInt32();
-            int format = parcelReader.ReadInt32();
-            int usage  = parcelReader.ReadInt32();
-
-            int slot = GetFreeSlotBlocking(width, height);
-
-            MultiFence multiFence = MultiFence.NoFence;
-
-            using (MemoryStream ms = new MemoryStream())
-            {
-                BinaryWriter writer = new BinaryWriter(ms);
-
-                // Allocated slot
-                writer.Write(slot);
-
-                // Has multi fence
-                writer.Write(1);
-
-                // Write the multi fnece
-                WriteFlattenedObject(writer, multiFence);
-
-                // Padding
-                writer.Write(0);
-
-                // Status
-                writer.Write(0);
-
-                return MakeReplyParcel(context, ms.ToArray());
-            }
-        }
-
-        private ResultCode GbpQueueBuffer(ServiceCtx context, BinaryReader parcelReader)
-        {
-            context.Device.Statistics.RecordGameFrameTime();
-
-            // TODO: Errors.
-            int slot            = parcelReader.ReadInt32();
-
-            long Position = parcelReader.BaseStream.Position;
-
-            QueueBufferObject queueBufferObject = ReadFlattenedObject<QueueBufferObject>(parcelReader);
-
-            parcelReader.BaseStream.Position = Position;
-
-            _bufferQueue[slot].Transform = queueBufferObject.Transform;
-            _bufferQueue[slot].Fence     = queueBufferObject.Fence;
-            _bufferQueue[slot].Crop      = queueBufferObject.Crop;
-            _bufferQueue[slot].State     = BufferState.Queued;
-
-            SendFrameBuffer(context, slot);
-
-            if (context.Device.EnableDeviceVsync)
-            {
-                context.Device.VsyncEvent.WaitOne();
-            }
-
-            return MakeReplyParcel(context, 1280, 720, 0, 0, 0);
-        }
-
-        private ResultCode GbpDetachBuffer(ServiceCtx context, BinaryReader parcelReader)
-        {
-            return MakeReplyParcel(context, 0);
-        }
-
-        private ResultCode GbpCancelBuffer(ServiceCtx context, BinaryReader parcelReader)
-        {
-            // TODO: Errors.
-            int slot = parcelReader.ReadInt32();
-
-            MultiFence fence = ReadFlattenedObject<MultiFence>(parcelReader);
-
-            _bufferQueue[slot].State = BufferState.Free;
-
-            _waitBufferFree.Set();
-
-            return MakeReplyParcel(context, 0);
-        }
-
-        private ResultCode GbpQuery(ServiceCtx context, BinaryReader parcelReader)
-        {
-            return MakeReplyParcel(context, 0, 0);
-        }
-
-        private ResultCode GbpConnect(ServiceCtx context, BinaryReader parcelReader)
-        {
-            return MakeReplyParcel(context, 1280, 720, 0, 0, 0);
-        }
-
-        private ResultCode GbpDisconnect(ServiceCtx context, BinaryReader parcelReader)
-        {
-            return MakeReplyParcel(context, 0);
-        }
-
-        private ResultCode GbpPreallocBuffer(ServiceCtx context, BinaryReader parcelReader)
-        {
-            int slot = parcelReader.ReadInt32();
-
-            bool hasInput = parcelReader.ReadInt32() == 1;
-
-            if (hasInput)
-            {
-                byte[] graphicBuffer = ReadFlattenedObject(parcelReader);
-
-                _bufferQueue[slot].State = BufferState.Free;
-
-                using (BinaryReader graphicBufferReader = new BinaryReader(new MemoryStream(graphicBuffer)))
-                {
-                    _bufferQueue[slot].Data = new GbpBuffer(graphicBufferReader);
-                }
-
-            }
-
-            return MakeReplyParcel(context, 0);
-        }
-
-        private byte[] ReadFlattenedObject(BinaryReader reader)
-        {
-            long flattenedObjectSize = reader.ReadInt64();
-
-            return reader.ReadBytes((int)flattenedObjectSize);
-        }
-
-        private T ReadFlattenedObject<T>(BinaryReader reader) where T: struct
-        {
-            long flattenedObjectSize = reader.ReadInt64();
-
-            Debug.Assert(flattenedObjectSize == Unsafe.SizeOf<T>());
-
-            return reader.ReadStruct<T>();
-        }
-
-        private unsafe void WriteFlattenedObject<T>(BinaryWriter writer, T value) where T : struct
-        {
-            writer.Write(Unsafe.SizeOf<T>());
-            writer.WriteStruct(value);
-        }
-
-        private ResultCode MakeReplyParcel(ServiceCtx context, params int[] ints)
-        {
-            using (MemoryStream ms = new MemoryStream())
-            {
-                BinaryWriter writer = new BinaryWriter(ms);
-
-                foreach (int Int in ints)
-                {
-                    writer.Write(Int);
-                }
-
-                return MakeReplyParcel(context, ms.ToArray());
-            }
-        }
-
-        private ResultCode MakeReplyParcel(ServiceCtx context, byte[] data)
-        {
-            (long replyPos, long replySize) = context.Request.GetBufferType0x22();
-
-            byte[] reply = MakeParcel(data, new byte[0]);
-
-            context.Memory.WriteBytes(replyPos, reply);
-
-            return ResultCode.Success;
-        }
-
-        private Format ConvertColorFormat(ColorFormat colorFormat)
-        {
-            switch (colorFormat)
-            {
-                case ColorFormat.A8B8G8R8:
-                    return Format.R8G8B8A8Unorm;
-                case ColorFormat.X8B8G8R8:
-                    return Format.R8G8B8A8Unorm;
-                case ColorFormat.R5G6B5:
-                    return Format.B5G6R5Unorm;
-                case ColorFormat.A8R8G8B8:
-                    return Format.B8G8R8A8Unorm;
-                case ColorFormat.A4B4G4R4:
-                    return Format.R4G4B4A4Unorm;
-                default:
-                    throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!");
-            }
-        }
-
-        // TODO: support multi surface
-        private void SendFrameBuffer(ServiceCtx context, int slot)
-        {
-            int fbWidth  = _bufferQueue[slot].Data.Header.Width;
-            int fbHeight = _bufferQueue[slot].Data.Header.Height;
-
-            int nvMapHandle = _bufferQueue[slot].Data.Buffer.Surfaces[0].NvMapHandle;
-
-            if (nvMapHandle == 0)
-            {
-                nvMapHandle = _bufferQueue[slot].Data.Buffer.NvMapId;
-            }
-
-            int bufferOffset = _bufferQueue[slot].Data.Buffer.Surfaces[0].Offset;
-
-            NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(context.Process, nvMapHandle);
-
-            ulong fbAddr = (ulong)(map.Address + bufferOffset);
-
-            _bufferQueue[slot].State = BufferState.Acquired;
-
-            Format format = ConvertColorFormat(_bufferQueue[slot].Data.Buffer.Surfaces[0].ColorFormat);
-
-            int bytesPerPixel =
-                format == Format.B5G6R5Unorm ||
-                format == Format.R4G4B4A4Unorm ? 2 : 4;
-
-            int gobBlocksInY = 1 << _bufferQueue[slot].Data.Buffer.Surfaces[0].BlockHeightLog2;
-
-            // Note: Rotation is being ignored.
-
-            Rect cropRect = _bufferQueue[slot].Crop;
-
-            bool flipX = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipX);
-            bool flipY = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipY);
-
-            ImageCrop crop = new ImageCrop(
-                cropRect.Left,
-                cropRect.Right,
-                cropRect.Top,
-                cropRect.Bottom,
-                flipX,
-                flipY);
-
-            context.Device.Gpu.Window.EnqueueFrameThreadSafe(
-                fbAddr,
-                fbWidth,
-                fbHeight,
-                0,
-                false,
-                gobBlocksInY,
-                format,
-                bytesPerPixel,
-                crop,
-                AcquireBuffer,
-                ReleaseBuffer,
-                slot);
-        }
-
-        private void AcquireBuffer(GpuContext context, object slot)
-        {
-            AcquireBuffer(context, (int)slot);
-        }
-
-        private void AcquireBuffer(GpuContext context, int slot)
-        {
-            _bufferQueue[slot].Fence.WaitForever(context);
-        }
-
-        private void ReleaseBuffer(object slot)
-        {
-            ReleaseBuffer((int)slot);
-        }
-
-        private void ReleaseBuffer(int slot)
-        {
-            _bufferQueue[slot].State = BufferState.Free;
-
-            _binderEvent.ReadableEvent.Signal();
-
-            _waitBufferFree.Set();
-        }
-
-        private int GetFreeSlotBlocking(int width, int height)
-        {
-            int slot;
-
-            do
-            {
-                if ((slot = GetFreeSlot(width, height)) != -1)
-                {
-                    break;
-                }
-
-                if (_disposed)
-                {
-                    break;
-                }
-
-                _waitBufferFree.WaitOne();
-            }
-            while (!_disposed);
-
-            return slot;
-        }
-
-        private int GetFreeSlot(int width, int height)
-        {
-            lock (_bufferQueue)
-            {
-                for (int slot = 0; slot < _bufferQueue.Length; slot++)
-                {
-                    if (_bufferQueue[slot].State != BufferState.Free)
-                    {
-                        continue;
-                    }
-
-                    GbpBuffer data = _bufferQueue[slot].Data;
-
-                    if (data.Header.Width  == width &&
-                        data.Header.Height == height)
-                    {
-                        _bufferQueue[slot].State = BufferState.Dequeued;
-
-                        return slot;
-                    }
-                }
-            }
-
-            return -1;
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing && !_disposed)
-            {
-                _disposed = true;
-
-                _waitBufferFree.Set();
-                _waitBufferFree.Dispose();
-            }
-        }
-    }
-}

+ 189 - 31
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs

@@ -1,58 +1,216 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Utilities;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
 using System;
-using System.IO;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
 
 namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 {
-    static class Parcel
+    class Parcel
     {
-        public static byte[] GetParcelData(byte[] parcel)
+        private readonly byte[] _rawData;
+
+        private Span<byte> Raw => new Span<byte>(_rawData);
+
+        private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(_rawData)[0];
+
+        private Span<byte> Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize);
+
+        private Span<byte> Objects => Raw.Slice((int)Header.ObjectOffset, (int)Header.ObjectsSize);
+
+        private int _payloadPosition;
+        private int _objectPosition;
+
+        public Parcel(byte[] rawData)
+        {
+            _rawData  = rawData;
+
+            _payloadPosition = 0;
+            _objectPosition  = 0;
+        }
+
+        public Parcel(uint payloadSize, uint objectsSize)
         {
-            if (parcel == null)
+            uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
+
+            _rawData = new byte[BitUtils.AlignUp(headerSize + payloadSize + objectsSize, 4)];
+
+            Header.PayloadSize   = payloadSize;
+            Header.ObjectsSize   = objectsSize;
+            Header.PayloadOffset = headerSize;
+            Header.ObjectOffset  = Header.PayloadOffset + Header.ObjectsSize;
+        }
+
+        public string ReadInterfaceToken()
+        {
+            // Ignore the policy flags
+            int strictPolicy = ReadInt32();
+
+            return ReadString16();
+        }
+
+        public string ReadString16()
+        {
+            int size = ReadInt32();
+
+            if (size < 0)
             {
-                throw new ArgumentNullException(nameof(parcel));
+                return "";
             }
 
-            using (MemoryStream ms = new MemoryStream(parcel))
+            ReadOnlySpan<byte> data = ReadInPlace((size + 1) * 2);
+
+            // Return the unicode string without the last character (null terminator)
+            return Encoding.Unicode.GetString(data.Slice(0, size * 2));
+        }
+
+        public int ReadInt32() => ReadUnmanagedType<int>();
+        public uint ReadUInt32() => ReadUnmanagedType<uint>();
+        public bool ReadBoolean() => ReadUnmanagedType<uint>() != 0;
+        public long ReadInt64() => ReadUnmanagedType<long>();
+        public ulong ReadUInt64() => ReadUnmanagedType<ulong>();
+
+        public T ReadFlattenable<T>() where T : unmanaged, IFlattenable
+        {
+            long flattenableSize = ReadInt64();
+
+            T result = new T();
+
+            Debug.Assert(flattenableSize == result.GetFlattenedSize());
+
+            result.Unflatten(this);
+
+            return result;
+        }
+
+        public T ReadUnmanagedType<T>() where T: unmanaged
+        {
+            ReadOnlySpan<byte> data = ReadInPlace(Unsafe.SizeOf<T>());
+
+            return MemoryMarshal.Cast<byte, T>(data)[0];
+        }
+
+        public ReadOnlySpan<byte> ReadInPlace(int size)
+        {
+            ReadOnlySpan<byte> result = Payload.Slice(_payloadPosition, size);
+
+            _payloadPosition += BitUtils.AlignUp(size, 4);
+
+            return result;
+        }
+
+        [StructLayout(LayoutKind.Sequential, Size = 0x28)]
+        private struct FlatBinderObject
+        {
+            public int  Type;
+            public int  Flags;
+            public long BinderId;
+            public long Cookie;
+
+            private byte _serviceNameStart;
+
+            public Span<byte> ServiceName => MemoryMarshal.CreateSpan(ref _serviceNameStart, 0x8);
+        }
+
+        public void WriteObject<T>(T obj, string serviceName) where T: IBinder
+        {
+            FlatBinderObject flatBinderObject = new FlatBinderObject
             {
-                BinaryReader reader = new BinaryReader(ms);
+                Type     = 2,
+                Flags    = 0,
+                BinderId = HOSBinderDriverServer.GetBinderId(obj),
+            };
 
-                int dataSize   = reader.ReadInt32();
-                int dataOffset = reader.ReadInt32();
-                int objsSize   = reader.ReadInt32();
-                int objsOffset = reader.ReadInt32();
+            Encoding.ASCII.GetBytes(serviceName).CopyTo(flatBinderObject.ServiceName);
 
-                ms.Seek(dataOffset - 0x10, SeekOrigin.Current);
+            WriteUnmanagedType(ref flatBinderObject);
 
-                return reader.ReadBytes(dataSize);
-            }
+            // TODO: figure out what this value is
+
+            WriteInplaceObject(new byte[4] { 0, 0, 0, 0 });
         }
 
-        public static byte[] MakeParcel(byte[] data, byte[] objs)
+        public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable
         {
-            if (data == null)
+            bool hasObject = ReadBoolean();
+
+            if (hasObject)
             {
-                throw new ArgumentNullException(nameof(data));
-            }
+                T obj = ReadFlattenable<T>();
 
-            if (objs == null)
+                return new AndroidStrongPointer<T>(obj);
+            }
+            else
             {
-                throw new ArgumentNullException(nameof(objs));
+                return new AndroidStrongPointer<T>();
             }
+        }
 
-            using (MemoryStream ms = new MemoryStream())
+        public void WriteStrongPointer<T>(ref AndroidStrongPointer<T> value) where T: unmanaged, IFlattenable
+        {
+            WriteBoolean(!value.IsNull);
+
+            if (!value.IsNull)
             {
-                BinaryWriter writer = new BinaryWriter(ms);
+                WriteFlattenable<T>(ref value.Object);
+            }
+        }
 
-                writer.Write(data.Length);
-                writer.Write(0x10);
-                writer.Write(objs.Length);
-                writer.Write(data.Length + 0x10);
+        public void WriteFlattenable<T>(ref T value) where T : unmanaged, IFlattenable
+        {
+            WriteInt64(value.GetFlattenedSize());
 
-                writer.Write(data);
-                writer.Write(objs);
+            value.Flatten(this);
+        }
 
-                return ms.ToArray();
-            }
+        public void WriteStatus(Status status) => WriteUnmanagedType(ref status);
+        public void WriteBoolean(bool value) => WriteUnmanagedType(ref value);
+        public void WriteInt32(int value) => WriteUnmanagedType(ref value);
+        public void WriteUInt32(uint value) => WriteUnmanagedType(ref value);
+        public void WriteInt64(long value) => WriteUnmanagedType(ref value);
+        public void WriteUInt64(ulong value) => WriteUnmanagedType(ref value);
+
+        public void WriteUnmanagedType<T>(ref T value) where T : unmanaged
+        {
+            WriteInplace(SpanHelpers.AsByteSpan(ref value));
+        }
+
+        public void WriteInplace(ReadOnlySpan<byte> data)
+        {
+            Span<byte> result = Payload.Slice(_payloadPosition, data.Length);
+
+            data.CopyTo(result);
+
+            _payloadPosition += BitUtils.AlignUp(data.Length, 4);
+        }
+
+        public void WriteInplaceObject(ReadOnlySpan<byte> data)
+        {
+            Span<byte> result = Objects.Slice(_objectPosition, data.Length);
+
+            data.CopyTo(result);
+
+            _objectPosition += BitUtils.AlignUp(data.Length, 4);
+        }
+
+        private void UpdateHeader()
+        {
+            uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
+
+            Header.PayloadSize   = (uint)_payloadPosition;
+            Header.ObjectsSize   = (uint)_objectPosition;
+            Header.PayloadOffset = headerSize;
+            Header.ObjectOffset  = Header.PayloadOffset + Header.PayloadSize;
+        }
+
+        public ReadOnlySpan<byte> Finish()
+        {
+            UpdateHeader();
+
+            return Raw.Slice(0, (int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf<ParcelHeader>()));
         }
     }
-}
+}

+ 10 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs

@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    struct ParcelHeader
+    {
+        public uint PayloadSize;
+        public uint PayloadOffset;
+        public uint ObjectsSize;
+        public uint ObjectOffset;
+    }
+}

+ 14 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs

@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    enum PixelFormat : uint
+    {
+        Unknown,
+        Rgba8888,
+        Rgbx8888,
+        Rgb888,
+        Rgb565,
+        Bgra8888,
+        Rgba5551,
+        Rgba4444,
+    }
+}

+ 22 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs

@@ -0,0 +1,22 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    enum Status : int
+    {
+        Success          = 0,
+        WouldBlock       = -11,
+        NoMemory         = -12,
+        Busy             = -16,
+        NoInit           = -19,
+        BadValue         = -22,
+        InvalidOperation = -37,
+
+        // Producer flags
+        BufferNeedsReallocation = 1,
+        ReleaseAllBuffers       = 2,
+
+        // Consumer errors
+        StaleBufferSlot    = 1,
+        NoBufferAvailaible = 2,
+        PresentLater       = 3,
+    }
+}

+ 376 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs

@@ -0,0 +1,376 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
+using Ryujinx.HLE.HOS.Services.Nv.Types;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    class SurfaceFlinger : IConsumerListener, IDisposable
+    {
+        private const int TargetFps = 60;
+
+        private Switch _device;
+
+        private Dictionary<long, Layer> _layers;
+
+        private bool _isRunning;
+
+        private Thread _composerThread;
+
+        private Stopwatch _chrono;
+
+        private AndroidFence _vblankFence;
+
+        private long _ticks;
+        private long _ticksPerFrame;
+
+        private int _swapInterval;
+
+        private readonly object Lock = new object();
+
+        public long LastId { get; private set; }
+
+        private class Layer
+        {
+            public int                    ProducerBinderId;
+            public IGraphicBufferProducer Producer;
+            public BufferItemConsumer     Consumer;
+            public KProcess               Owner;
+        }
+
+        private class TextureCallbackInformation
+        {
+            public Layer        Layer;
+            public BufferItem   Item;
+            public AndroidFence Fence;
+        }
+
+        public SurfaceFlinger(Switch device)
+        {
+            _device = device;
+            _layers = new Dictionary<long, Layer>();
+            LastId  = 0;
+
+            _composerThread = new Thread(HandleComposition)
+            {
+                Name = "SurfaceFlinger.Composer"
+            };
+
+            _chrono = new Stopwatch();
+
+            _ticks = 0;
+
+            UpdateSwapInterval(1);
+
+            _vblankFence = AndroidFence.NoFence;
+            _vblankFence.AddFence(new NvFence
+            {
+                Id    = NvHostSyncpt.VBlank0SyncpointId,
+                Value = 0
+            });
+
+            _composerThread.Start();
+        }
+
+        private void UpdateSwapInterval(int swapInterval)
+        {
+            _swapInterval = swapInterval;
+
+            // If the swap interval is 0, Game VSync is disabled.
+            if (_swapInterval == 0)
+            {
+                _ticksPerFrame = 1;
+            }
+            else
+            {
+                _ticksPerFrame = Stopwatch.Frequency / (TargetFps / _swapInterval);
+            }
+        }
+
+        public IGraphicBufferProducer OpenLayer(KProcess process, long layerId)
+        {
+            bool needCreate;
+
+            lock (Lock)
+            {
+                needCreate = GetLayerByIdLocked(layerId) == null;
+            }
+
+            if (needCreate)
+            {
+                CreateLayerFromId(process, layerId);
+            }
+
+            return GetProducerByLayerId(layerId);
+        }
+
+        public IGraphicBufferProducer CreateLayer(KProcess process, out long layerId)
+        {
+            layerId = 1;
+
+            lock (Lock)
+            {
+                foreach (KeyValuePair<long, Layer> pair in _layers)
+                {
+                    if (pair.Key >= layerId)
+                    {
+                        layerId = pair.Key + 1;
+                    }
+                }
+            }
+
+            CreateLayerFromId(process, layerId);
+
+            return GetProducerByLayerId(layerId);
+        }
+
+        private void CreateLayerFromId(KProcess process, long layerId)
+        {
+            lock (Lock)
+            {
+                Logger.PrintInfo(LogClass.SurfaceFlinger, $"Creating layer {layerId}");
+
+                BufferQueue.CreateBufferQueue(_device, process, out BufferQueueProducer producer, out BufferQueueConsumer consumer);
+
+                _layers.Add(layerId, new Layer
+                {
+                    ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer),
+                    Producer         = producer,
+                    Consumer         = new BufferItemConsumer(_device, consumer, 0, -1, false, this),
+                    Owner            = process
+                });
+
+                LastId = layerId;
+            }
+        }
+
+        public bool CloseLayer(long layerId)
+        {
+            lock (Lock)
+            {
+                Layer layer = GetLayerByIdLocked(layerId);
+
+                if (layer != null)
+                {
+                    HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
+                }
+
+                return _layers.Remove(layerId);
+            }
+        }
+
+        private Layer GetLayerByIdLocked(long layerId)
+        {
+            foreach (KeyValuePair<long, Layer> pair in _layers)
+            {
+                if (pair.Key == layerId)
+                {
+                    return pair.Value;
+                }
+            }
+
+            return null;
+        }
+
+        public IGraphicBufferProducer GetProducerByLayerId(long layerId)
+        {
+            lock (Lock)
+            {
+                Layer layer = GetLayerByIdLocked(layerId);
+
+                if (layer != null)
+                {
+                    return layer.Producer;
+                }
+            }
+
+            return null;
+        }
+
+        private void HandleComposition()
+        {
+            _isRunning = true;
+
+            while (_isRunning)
+            {
+                _ticks += _chrono.ElapsedTicks;
+
+                _chrono.Restart();
+
+                if (_ticks >= _ticksPerFrame)
+                {
+                    Compose();
+
+                    _device.System.SignalVsync();
+
+                    _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
+                }
+
+                // Sleep the minimal amount of time to avoid being too expensive.
+                Thread.Sleep(1);
+            }
+        }
+
+        public void Compose()
+        {
+            lock (Lock)
+            {
+                _vblankFence.NvFences[0].Increment(_device.Gpu);
+
+                // TODO: support multilayers (& multidisplay ?)
+                if (_layers.Count == 0)
+                {
+                    return;
+                }
+
+                Layer layer = GetLayerByIdLocked(LastId);
+
+                Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
+
+                if (acquireStatus == Status.Success)
+                {
+                    // If device vsync is disabled, reflect the change.
+                    if (!_device.EnableDeviceVsync)
+                    {
+                        if (_swapInterval != 0)
+                        {
+                            UpdateSwapInterval(0);
+                        }
+                    }
+                    else if (item.SwapInterval != _swapInterval)
+                    {
+                        UpdateSwapInterval(item.SwapInterval);
+                    }
+
+                    PostFrameBuffer(layer, item);
+                }
+                else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation)
+                {
+                    throw new InvalidOperationException();
+                }
+            }
+        }
+
+        private void PostFrameBuffer(Layer layer, BufferItem item)
+        { 
+            int frameBufferWidth  = item.GraphicBuffer.Object.Width;
+            int frameBufferHeight = item.GraphicBuffer.Object.Height;
+
+            int nvMapHandle = item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
+
+            if (nvMapHandle == 0)
+            {
+                nvMapHandle = item.GraphicBuffer.Object.Buffer.NvMapId;
+            }
+
+            int bufferOffset = item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
+
+            NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle);
+
+            ulong frameBufferAddress = (ulong)(map.Address + bufferOffset);
+
+            Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
+
+            int bytesPerPixel =
+                format == Format.B5G6R5Unorm ||
+                format == Format.R4G4B4A4Unorm ? 2 : 4;
+
+            int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2;
+
+            // Note: Rotation is being ignored.
+            Rect cropRect = item.Crop;
+
+            bool flipX = item.Transform.HasFlag(NativeWindowTransform.FlipX);
+            bool flipY = item.Transform.HasFlag(NativeWindowTransform.FlipY);
+
+            ImageCrop crop = new ImageCrop(
+                cropRect.Left,
+                cropRect.Right,
+                cropRect.Top,
+                cropRect.Bottom,
+                flipX,
+                flipY);
+
+            // Enforce that dequeueBuffer wait for the next vblank
+            _vblankFence.NvFences[0].Value++;
+
+            TextureCallbackInformation textureCallbackInformation = new TextureCallbackInformation
+            {
+                Layer = layer,
+                Item  = item,
+                Fence = _vblankFence
+            };
+
+            _device.Gpu.Window.EnqueueFrameThreadSafe(
+                frameBufferAddress,
+                frameBufferWidth,
+                frameBufferHeight,
+                0,
+                false,
+                gobBlocksInY,
+                format,
+                bytesPerPixel,
+                crop,
+                AcquireBuffer,
+                ReleaseBuffer,
+                textureCallbackInformation);
+        }
+
+        private void ReleaseBuffer(object obj)
+        {
+            ReleaseBuffer((TextureCallbackInformation)obj);
+        }
+
+        private void ReleaseBuffer(TextureCallbackInformation information)
+        {
+            information.Layer.Consumer.ReleaseBuffer(information.Item, ref information.Fence);
+        }
+
+        private void AcquireBuffer(GpuContext ignored, object obj)
+        {
+            AcquireBuffer((TextureCallbackInformation)obj);
+        }
+
+        private void AcquireBuffer(TextureCallbackInformation information)
+        {
+            information.Item.Fence.WaitForever(_device.Gpu);
+        }
+
+        public static Format ConvertColorFormat(ColorFormat colorFormat)
+        {
+            return colorFormat switch
+            {
+                ColorFormat.A8B8G8R8 => Format.R8G8B8A8Unorm,
+                ColorFormat.X8B8G8R8 => Format.R8G8B8A8Unorm,
+                ColorFormat.R5G6B5 => Format.B5G6R5Unorm,
+                ColorFormat.A8R8G8B8 => Format.B8G8R8A8Unorm,
+                ColorFormat.A4B4G4R4 => Format.R4G4B4A4Unorm,
+                _ => throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"),
+            };
+        }
+
+        public void Dispose()
+        {
+            _isRunning = false;
+        }
+
+        public void OnFrameAvailable(ref BufferItem item)
+        {
+            _device.Statistics.RecordGameFrameTime();
+        }
+
+        public void OnFrameReplaced(ref BufferItem item)
+        {
+            _device.Statistics.RecordGameFrameTime();
+        }
+
+        public void OnBuffersReleased() {}
+    }
+}

+ 89 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs

@@ -0,0 +1,89 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.HLE.HOS.Services.Nv.Types;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)]
+    struct AndroidFence : IFlattenable
+    {
+        public int FenceCount;
+
+        private byte _fenceStorageStart;
+
+        private Span<byte> _storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf<NvFence>() * 4);
+
+        public Span<NvFence> NvFences => MemoryMarshal.Cast<byte, NvFence>(_storage);
+
+        public static AndroidFence NoFence
+        {
+            get
+            {
+                AndroidFence fence = new AndroidFence
+                {
+                    FenceCount = 0
+                };
+
+                fence.NvFences[0].Id = NvFence.InvalidSyncPointId;
+
+                return fence;
+            }
+        }
+
+        public void AddFence(NvFence fence)
+        {
+            NvFences[FenceCount++] = fence;
+        }
+
+        public void WaitForever(GpuContext gpuContext)
+        {
+            bool hasTimeout = Wait(gpuContext, TimeSpan.FromMilliseconds(3000));
+
+            if (hasTimeout)
+            {
+                Logger.PrintError(LogClass.SurfaceFlinger, "Android fence didn't signal in 3000 ms");
+                Wait(gpuContext, Timeout.InfiniteTimeSpan);
+            }
+
+        }
+
+        public bool Wait(GpuContext gpuContext, TimeSpan timeout)
+        {
+            for (int i = 0; i < FenceCount; i++)
+            {
+                bool hasTimeout = NvFences[i].Wait(gpuContext, timeout);
+
+                if (hasTimeout)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public uint GetFlattenedSize()
+        {
+            return (uint)Unsafe.SizeOf<AndroidFence>();
+        }
+
+        public uint GetFdCount()
+        {
+            return 0;
+        }
+
+        public void Flatten(Parcel parcel)
+        {
+            parcel.WriteUnmanagedType(ref this);
+        }
+
+        public void Unflatten(Parcel parcel)
+        {
+            this = parcel.ReadUnmanagedType<AndroidFence>();
+        }
+    }
+}

+ 38 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs

@@ -0,0 +1,38 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types
+{
+    class AndroidStrongPointer<T> where T: unmanaged, IFlattenable
+    {
+        public T Object;
+
+        private bool _hasObject;
+
+        public bool IsNull => !_hasObject;
+
+        public AndroidStrongPointer()
+        {
+            _hasObject = false;
+        }
+
+        public AndroidStrongPointer(T obj)
+        {
+            Set(obj);
+        }
+
+        public void Set(AndroidStrongPointer<T> other)
+        {
+            Object     = other.Object;
+            _hasObject = other._hasObject;
+        }
+
+        public void Set(T obj)
+        {
+            Object     = obj;
+            _hasObject = true;
+        }
+
+        public void Reset()
+        {
+            _hasObject = false;
+        }
+    }
+}

+ 0 - 15
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs

@@ -1,15 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
-{
-    struct BufferEntry
-    {
-        public BufferState State;
-
-        public HalTransform Transform;
-
-        public Rect Crop;
-
-        public MultiFence Fence;
-
-        public GbpBuffer Data;
-    }
-}

+ 5 - 5
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs

@@ -1,10 +1,10 @@
 namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 {
-    enum BufferState
+    internal enum BufferState
     {
-        Free,
-        Dequeued,
-        Queued,
-        Acquired
+        Free     = 0,
+        Dequeued = 1,
+        Queued   = 2,
+        Acquired = 3
     }
 }

+ 0 - 37
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs

@@ -1,37 +0,0 @@
-using Ryujinx.Common;
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
-{
-    struct GbpBuffer
-    {
-        public GraphicBufferHeader Header { get; private set; }
-        public NvGraphicBuffer     Buffer { get; private set; }
-
-        public int Size => Marshal.SizeOf<NvGraphicBuffer>() + Marshal.SizeOf<GraphicBufferHeader>();
-
-        public GbpBuffer(BinaryReader reader)
-        {
-            Header = reader.ReadStruct<GraphicBufferHeader>();
-
-            // ignore fds
-            // TODO: check if that is used in official implementation
-            reader.BaseStream.Position += Header.FdsCount * 4;
-
-            if (Header.IntsCount != 0x51)
-            {
-                throw new NotImplementedException($"Unexpected Graphic Buffer ints count (expected 0x51, found 0x{Header.IntsCount:x}");
-            }
-
-            Buffer = reader.ReadStruct<NvGraphicBuffer>();
-        }
-
-        public void Write(BinaryWriter writer)
-        {
-            writer.WriteStruct(Header);
-            writer.WriteStruct(Buffer);
-        }
-    }
-}

+ 75 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs

@@ -0,0 +1,75 @@
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    struct GraphicBuffer : IFlattenable
+    {
+        public GraphicBufferHeader Header;
+        public NvGraphicBuffer     Buffer;
+
+        public int Width => Header.Width;
+        public int Height => Header.Height;
+        public PixelFormat Format => Header.Format;
+        public int Usage => Header.Usage;
+
+        public Rect ToRect()
+        {
+            return new Rect(Width, Height);
+        }
+
+        public void Flatten(Parcel parcel)
+        {
+            parcel.WriteUnmanagedType(ref Header);
+            parcel.WriteUnmanagedType(ref Buffer);
+        }
+
+        public void Unflatten(Parcel parcel)
+        {
+            Header = parcel.ReadUnmanagedType<GraphicBufferHeader>();
+
+            int expectedSize = Unsafe.SizeOf<NvGraphicBuffer>() / 4;
+
+            if (Header.IntsCount != expectedSize)
+            {
+                throw new NotImplementedException($"Unexpected Graphic Buffer ints count (expected 0x{expectedSize:x}, found 0x{Header.IntsCount:x})");
+            }
+
+            Buffer = parcel.ReadUnmanagedType<NvGraphicBuffer>();
+        }
+
+        public void IncrementNvMapHandleRefCount(KProcess process)
+        {
+            NvMapDeviceFile.IncrementMapRefCount(process, Buffer.NvMapId);
+
+            for (int i = 0; i < Buffer.Surfaces.Length; i++)
+            {
+                NvMapDeviceFile.IncrementMapRefCount(process, Buffer.Surfaces[i].NvMapHandle);
+            }
+        }
+
+        public void DecrementNvMapHandleRefCount(KProcess process)
+        {
+            NvMapDeviceFile.DecrementMapRefCount(process, Buffer.NvMapId);
+
+            for (int i = 0; i < Buffer.Surfaces.Length; i++)
+            {
+                NvMapDeviceFile.DecrementMapRefCount(process, Buffer.Surfaces[i].NvMapHandle);
+            }
+        }
+
+        public uint GetFlattenedSize()
+        {
+            return (uint)Unsafe.SizeOf<GraphicBuffer>();
+        }
+
+        public uint GetFdCount()
+        {
+            return 0;
+        }
+    }
+}

+ 7 - 7
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs

@@ -2,15 +2,15 @@
 
 namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 {
-    [StructLayout(LayoutKind.Sequential, Size = 0x28)]
+    [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 1)]
     struct GraphicBufferHeader
     {
-        public int Magic;
-        public int Width;
-        public int Height;
-        public int Stride;
-        public int Format;
-        public int Usage;
+        public int         Magic;
+        public int         Width;
+        public int         Height;
+        public int         Stride;
+        public PixelFormat Format;
+        public int         Usage;
 
         public int Pid;
         public int RefCount;

+ 0 - 14
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs

@@ -1,14 +0,0 @@
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
-{
-    [Flags]
-    enum HalTransform
-    {
-        FlipX     = 1,
-        FlipY     = 2,
-        Rotate90  = 4,
-        Rotate180 = FlipX    | FlipY,
-        Rotate270 = Rotate90 | Rotate180
-    }
-}

+ 0 - 49
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs

@@ -1,49 +0,0 @@
-using Ryujinx.Graphics.Gpu;
-using Ryujinx.HLE.HOS.Services.Nv.Types;
-using System;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading;
-
-namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
-{
-    [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)]
-    struct MultiFence
-    {
-        public int FenceCount;
-
-        private byte _fenceStorageStart;
-
-        private Span<byte> _storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf<NvFence>() * 4);
-
-        private Span<NvFence> _nvFences => MemoryMarshal.Cast<byte, NvFence>(_storage);
-
-        public static MultiFence NoFence
-        {
-            get
-            {
-                MultiFence fence = new MultiFence
-                {
-                    FenceCount = 0
-                };
-
-                fence._nvFences[0].Id = NvFence.InvalidSyncPointId;
-
-                return fence;
-            }
-        }
-
-        public void WaitForever(GpuContext gpuContext)
-        {
-            Wait(gpuContext, Timeout.InfiniteTimeSpan);
-        }
-
-        public void Wait(GpuContext gpuContext, TimeSpan timeout)
-        {
-            for (int i = 0; i < FenceCount; i++)
-            {
-                _nvFences[i].Wait(gpuContext, timeout);
-            }
-        }
-    }
-}

+ 1 - 1
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs

@@ -2,7 +2,7 @@
 
 namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 {
-    [StructLayout(LayoutKind.Explicit, Size = 0x144)]
+    [StructLayout(LayoutKind.Explicit, Size = 0x144, Pack = 1)]
     struct NvGraphicBuffer
     {
         [FieldOffset(0x4)]

+ 2 - 0
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs

@@ -35,5 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
                 throw new IndexOutOfRangeException();
             }
         }
+
+        public int Length => 3;
     }
 }

+ 0 - 35
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs

@@ -1,35 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
-{
-    [StructLayout(LayoutKind.Explicit, Pack = 1)]
-    struct QueueBufferObject
-    {
-        [FieldOffset(0x0)]
-        public long Timestamp;
-
-        [FieldOffset(0x8)]
-        public int IsAutoTimestamp;
-
-        [FieldOffset(0xC)]
-        public Rect Crop;
-
-        [FieldOffset(0x1C)]
-        public int ScalingMode;
-
-        [FieldOffset(0x20)]
-        public HalTransform Transform;
-
-        [FieldOffset(0x24)]
-        public int StickyTransform;
-
-        [FieldOffset(0x28)]
-        public int Unknown;
-
-        [FieldOffset(0x2C)]
-        public int SwapInterval;
-
-        [FieldOffset(0x30)]
-        public MultiFence Fence;
-    }
-}

+ 61 - 3
Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs

@@ -1,13 +1,71 @@
-using System.Runtime.InteropServices;
+using System;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 {
     [StructLayout(LayoutKind.Sequential, Size = 0x10)]
-    struct Rect
+    struct Rect : IEquatable<Rect>
     {
-        public int Top;
         public int Left;
+        public int Top;
         public int Right;
         public int Bottom;
+
+        public int Width => Right - Left;
+        public int Height => Bottom - Top;
+
+        public Rect(int width, int height)
+        {
+            Left   = 0;
+            Top    = 0;
+            Right  = width;
+            Bottom = height;
+        }
+
+        public bool IsEmpty()
+        {
+            return Width <= 0 || Height <= 0;
+        }
+
+        public bool Intersect(Rect other, out Rect result)
+        {
+            result = new Rect
+            {
+                Left   = Math.Max(Left, other.Left),
+                Top    = Math.Max(Top, other.Top),
+                Right  = Math.Min(Right, other.Right),
+                Bottom = Math.Min(Bottom, other.Bottom)
+            };
+
+            return !result.IsEmpty();
+        }
+
+        public void MakeInvalid()
+        {
+            Right  = -1;
+            Bottom = -1;
+        }
+
+        public static bool operator ==(Rect x, Rect y)
+        {
+            return x.Equals(y);
+        }
+
+        public static bool operator !=(Rect x, Rect y)
+        {
+            return !x.Equals(y);
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is Rect rect && Equals(rect);
+        }
+
+        public bool Equals(Rect cmpObj)
+        {
+            return Left == cmpObj.Left && Top == cmpObj.Top && Right == cmpObj.Right && Bottom == cmpObj.Bottom;
+        }
+
+        public override int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom);
     }
 }

+ 0 - 99
Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs

@@ -1,99 +0,0 @@
-using Ryujinx.Graphics.GAL;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Common;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
-{
-    class IHOSBinderDriver : IpcService, IDisposable
-    {
-        private KEvent _binderEvent;
-
-        private NvFlinger _flinger;
-
-        public IHOSBinderDriver(Horizon system, IRenderer renderer)
-        {
-            _binderEvent = new KEvent(system);
-
-            _binderEvent.ReadableEvent.Signal();
-
-            _flinger = new NvFlinger(renderer, _binderEvent);
-        }
-
-        [Command(0)]
-        // TransactParcel(s32, u32, u32, buffer<unknown, 5, 0>) -> buffer<unknown, 6, 0>
-        public ResultCode TransactParcel(ServiceCtx context)
-        {
-            int id   = context.RequestData.ReadInt32();
-            int code = context.RequestData.ReadInt32();
-
-            long dataPos  = context.Request.SendBuff[0].Position;
-            long dataSize = context.Request.SendBuff[0].Size;
-
-            byte[] data = context.Memory.ReadBytes(dataPos, dataSize);
-
-            data = Parcel.GetParcelData(data);
-
-            return (ResultCode)_flinger.ProcessParcelRequest(context, data, code);
-        }
-
-        [Command(1)]
-        // AdjustRefcount(s32, s32, s32)
-        public ResultCode AdjustRefcount(ServiceCtx context)
-        {
-            int id     = context.RequestData.ReadInt32();
-            int addVal = context.RequestData.ReadInt32();
-            int type   = context.RequestData.ReadInt32();
-
-            return ResultCode.Success;
-        }
-
-        [Command(2)]
-        // GetNativeHandle(s32, s32) -> handle<copy>
-        public ResultCode GetNativeHandle(ServiceCtx context)
-        {
-            int  id  = context.RequestData.ReadInt32();
-            uint unk = context.RequestData.ReadUInt32();
-
-            if (context.Process.HandleTable.GenerateHandle(_binderEvent.ReadableEvent, out int handle) != KernelResult.Success)
-            {
-                throw new InvalidOperationException("Out of handles!");
-            }
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
-
-            return ResultCode.Success;
-        }
-
-        [Command(3)] // 3.0.0+
-        // TransactParcelAuto(s32, u32, u32, buffer<unknown, 21, 0>) -> buffer<unknown, 22, 0>
-        public ResultCode TransactParcelAuto(ServiceCtx context)
-        {
-            int id = context.RequestData.ReadInt32();
-            int code = context.RequestData.ReadInt32();
-
-            (long dataPos, long dataSize) = context.Request.GetBufferType0x21();
-
-            byte[] data = context.Memory.ReadBytes(dataPos, dataSize);
-
-            data = Parcel.GetParcelData(data);
-
-            return (ResultCode)_flinger.ProcessParcelRequest(context, data, code);
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                _flinger.Dispose();
-            }
-        }
-    }
-}

+ 9 - 5
Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs

@@ -1,4 +1,5 @@
 using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
 
 namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
 {
@@ -15,9 +16,12 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
         // CreateManagedLayer(u32, u64, nn::applet::AppletResourceUserId) -> u64
         public ResultCode CreateManagedLayer(ServiceCtx context)
         {
-            Logger.PrintStub(LogClass.ServiceVi);
+            long layerFlags = context.RequestData.ReadInt64();
+            long displayId  = context.RequestData.ReadInt64();
+
+            context.Device.System.SurfaceFlinger.CreateLayer(context.Process, out long layerId);
 
-            context.ResponseData.Write(0L); //LayerId
+            context.ResponseData.Write(layerId);
 
             return ResultCode.Success;
         }
@@ -26,7 +30,9 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
         // DestroyManagedLayer(u64)
         public ResultCode DestroyManagedLayer(ServiceCtx context)
         {
-            Logger.PrintStub(LogClass.ServiceVi);
+            long layerId = context.RequestData.ReadInt64();
+
+            context.Device.System.SurfaceFlinger.CloseLayer(layerId);
 
             return ResultCode.Success;
         }
@@ -35,8 +41,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
         // CreateStrayLayer(u32, u64) -> (u64, u64, buffer<bytes, 6>)
         public ResultCode CreateStrayLayer(ServiceCtx context)
         {
-            Logger.PrintStub(LogClass.ServiceVi);
-
             return _applicationDisplayService.CreateStrayLayer(context);
         }
 

+ 35 - 50
Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs

@@ -1,13 +1,11 @@
 using ARMeilleure.Memory;
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
 using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService;
 using System;
-using System.IO;
 using System.Text;
 
-using static Ryujinx.HLE.HOS.Services.SurfaceFlinger.Parcel;
-
 namespace Ryujinx.HLE.HOS.Services.Vi.RootService
 {
     class IApplicationDisplayService : IpcService
@@ -23,9 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
         // GetRelayService() -> object<nns::hosbinder::IHOSBinderDriver>
         public ResultCode GetRelayService(ServiceCtx context)
         {
-            MakeObject(context, new IHOSBinderDriver(
-                context.Device.System,
-                context.Device.Gpu.Renderer));
+            MakeObject(context, new HOSBinderDriverServer());
 
             return ResultCode.Success;
         }
@@ -52,9 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
         // GetIndirectDisplayTransactionService() -> object<nns::hosbinder::IHOSBinderDriver>
         public ResultCode GetIndirectDisplayTransactionService(ServiceCtx context)
         {
-            MakeObject(context, new IHOSBinderDriver(
-                context.Device.System,
-                context.Device.Gpu.Renderer));
+            MakeObject(context, new HOSBinderDriverServer());
 
             return ResultCode.Success;
         }
@@ -71,8 +65,8 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
             context.Memory.WriteBytes(recBuffPtr, Encoding.ASCII.GetBytes("Default"));
             context.Memory.WriteInt64(recBuffPtr + 0x40, 0x1L);
             context.Memory.WriteInt64(recBuffPtr + 0x48, 0x1L);
-            context.Memory.WriteInt64(recBuffPtr + 0x50, 1920L);
-            context.Memory.WriteInt64(recBuffPtr + 0x58, 1080L);
+            context.Memory.WriteInt64(recBuffPtr + 0x50, 1280L);
+            context.Memory.WriteInt64(recBuffPtr + 0x58, 720L);
 
             context.ResponseData.Write(1L);
 
@@ -119,16 +113,24 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
         // OpenLayer(nn::vi::DisplayName, u64, nn::applet::AppletResourceUserId, pid) -> (u64, buffer<bytes, 6>)
         public ResultCode OpenLayer(ServiceCtx context)
         {
-            long layerId = context.RequestData.ReadInt64();
-            long userId  = context.RequestData.ReadInt64();
+            // TODO: support multi display.
+            byte[] displayName = context.RequestData.ReadBytes(0x40);
 
+            long layerId   = context.RequestData.ReadInt64();
+            long userId    = context.RequestData.ReadInt64();
             long parcelPtr = context.Request.ReceiveBuff[0].Position;
 
-            byte[] parcel = MakeIGraphicsBufferProducer(parcelPtr);
+            IBinder producer = context.Device.System.SurfaceFlinger.OpenLayer(context.Process, layerId);
+
+            Parcel parcel = new Parcel(0x28, 0x4);
+
+            parcel.WriteObject(producer, "dispdrv\0");
+
+            ReadOnlySpan<byte> parcelData = parcel.Finish();
 
-            context.Memory.WriteBytes(parcelPtr, parcel);
+            context.Memory.WriteBytes(parcelPtr, parcelData.ToArray());
 
-            context.ResponseData.Write((long)parcel.Length);
+            context.ResponseData.Write((long)parcelData.Length);
 
             return ResultCode.Success;
         }
@@ -139,6 +141,8 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
         {
             long layerId = context.RequestData.ReadInt64();
 
+            context.Device.System.SurfaceFlinger.CloseLayer(layerId);
+
             return ResultCode.Success;
         }
 
@@ -151,14 +155,21 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
 
             long parcelPtr = context.Request.ReceiveBuff[0].Position;
 
+            // TODO: support multi display.
             Display disp = _displays.GetData<Display>((int)displayId);
 
-            byte[] parcel = MakeIGraphicsBufferProducer(parcelPtr);
+            IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(context.Process, out long layerId);
+
+            Parcel parcel = new Parcel(0x28, 0x4);
+
+            parcel.WriteObject(producer, "dispdrv\0");
+
+            ReadOnlySpan<byte> parcelData = parcel.Finish();
 
-            context.Memory.WriteBytes(parcelPtr, parcel);
+            context.Memory.WriteBytes(parcelPtr, parcelData.ToArray());
 
-            context.ResponseData.Write(0L);
-            context.ResponseData.Write((long)parcel.Length);
+            context.ResponseData.Write(layerId);
+            context.ResponseData.Write((long)parcelData.Length);
 
             return ResultCode.Success;
         }
@@ -167,6 +178,10 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
         // DestroyStrayLayer(u64)
         public ResultCode DestroyStrayLayer(ServiceCtx context)
         {
+            long layerId = context.RequestData.ReadInt64();
+
+            context.Device.System.SurfaceFlinger.CloseLayer(layerId);
+
             return ResultCode.Success;
         }
 
@@ -236,36 +251,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
             return ResultCode.Success;
         }
 
-        private byte[] MakeIGraphicsBufferProducer(long basePtr)
-        {
-            long id        = 0x20;
-            long cookiePtr = 0L;
-
-            using (MemoryStream ms = new MemoryStream())
-            {
-                BinaryWriter writer = new BinaryWriter(ms);
-
-                // flat_binder_object (size is 0x28)
-                writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER)
-                writer.Write(0); //Flags
-                writer.Write((int)(id >> 0));
-                writer.Write((int)(id >> 32));
-                writer.Write((int)(cookiePtr >> 0));
-                writer.Write((int)(cookiePtr >> 32));
-                writer.Write((byte)'d');
-                writer.Write((byte)'i');
-                writer.Write((byte)'s');
-                writer.Write((byte)'p');
-                writer.Write((byte)'d');
-                writer.Write((byte)'r');
-                writer.Write((byte)'v');
-                writer.Write((byte)'\0');
-                writer.Write(0L); //Pad
-
-                return MakeParcel(ms.ToArray(), new byte[] { 0, 0, 0, 0 });
-            }
-        }
-
         private string GetDisplayName(ServiceCtx context)
         {
             string name = string.Empty;

+ 0 - 5
Ryujinx.HLE/Switch.cs

@@ -32,8 +32,6 @@ namespace Ryujinx.HLE
 
         public bool EnableDeviceVsync { get; set; } = true;
 
-        public AutoResetEvent VsyncEvent { get; private set; }
-
         public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, IRenderer renderer, IAalOutput audioOut)
         {
             if (renderer == null)
@@ -60,8 +58,6 @@ namespace Ryujinx.HLE
 
             Hid = new Hid(this, System.HidBaseAddress);
             Hid.InitDevices();
-
-            VsyncEvent = new AutoResetEvent(true);
         }
 
         public void Initialize()
@@ -156,7 +152,6 @@ namespace Ryujinx.HLE
             if (disposing)
             {
                 System.Dispose();
-                VsyncEvent.Dispose();
                 AudioOut.Dispose();
             }
         }

+ 0 - 4
Ryujinx/Ui/GLRenderer.cs

@@ -349,10 +349,6 @@ namespace Ryujinx.Ui
                         $"Game: {_device.Statistics.GetGameFrameRate():00.00} FPS",
                         $"GPU: {_renderer.GpuVendor}"));
 
-                    _device.System.SignalVsync();
-
-                    _device.VsyncEvent.Set();
-
                     _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
                 }
             }