aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/SurfaceFlinger
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/SurfaceFlinger')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs95
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs420
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs341
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs871
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs175
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs109
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs304
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs109
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs221
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs548
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs104
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs38
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs62
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs42
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs235
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs31
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs74
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs71
43 files changed, 4341 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs
new file mode 100644
index 00000000..3b33bf8b
--- /dev/null
+++ b/src/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);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs
new file mode 100644
index 00000000..bc0901ab
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ static class BufferQueue
+ {
+ public static BufferQueueCore CreateBufferQueue(Switch device, ulong pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer)
+ {
+ BufferQueueCore core = new BufferQueueCore(device, pid);
+
+ producer = new BufferQueueProducer(core, device.System.TickSource);
+ consumer = new BufferQueueConsumer(core);
+
+ return core;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs
new file mode 100644
index 00000000..c9bb0a65
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs
@@ -0,0 +1,420 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ 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.MaxBufferCountCached; i++)
+ {
+ if (Core.Slots[i].BufferState == BufferState.Acquired)
+ {
+ numAcquiredBuffers++;
+ }
+ }
+
+ if (numAcquiredBuffers > Core.MaxAcquiredBufferCount)
+ {
+ bufferItem = null;
+
+ Logger.Debug?.Print(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;
+
+ ulong targetFrameNumber = Core.Slots[bufferItem.Slot].FrameNumber;
+
+ for (int i = 0; i < Core.BufferHistory.Length; i++)
+ {
+ if (Core.BufferHistory[i].FrameNumber == targetFrameNumber)
+ {
+ Core.BufferHistory[i].State = BufferState.Acquired;
+
+ break;
+ }
+ }
+ }
+
+ if (bufferItem.AcquireCalled)
+ {
+ 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.Error?.Print(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.Error?.Print(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})");
+
+ return Status.InvalidOperation;
+ }
+
+ if (freeSlot == BufferSlotArray.InvalidBufferSlot)
+ {
+ slot = BufferSlotArray.InvalidBufferSlot;
+
+ return Status.NoMemory;
+ }
+
+ Core.UpdateMaxBufferCountCachedLocked(freeSlot);
+
+ 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;
+ }
+
+ public Status SetPresentTime(int slot, ulong frameNumber, TimeSpanType presentationTime)
+ {
+ if (slot < 0 || slot >= Core.Slots.Length)
+ {
+ return Status.BadValue;
+ }
+
+ lock (Core.Lock)
+ {
+ if (Core.Slots[slot].FrameNumber != frameNumber)
+ {
+ return Status.StaleBufferSlot;
+ }
+
+ if (Core.Slots[slot].PresentationTime.NanoSeconds == 0)
+ {
+ Core.Slots[slot].PresentationTime = presentationTime;
+ }
+
+ for (int i = 0; i < Core.BufferHistory.Length; i++)
+ {
+ if (Core.BufferHistory[i].FrameNumber == frameNumber)
+ {
+ Core.BufferHistory[i].PresentationTime = presentationTime;
+
+ break;
+ }
+ }
+ }
+
+ return Status.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
new file mode 100644
index 00000000..1efd37f4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
@@ -0,0 +1,341 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+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 BufferInfo[] BufferHistory;
+ public uint BufferHistoryPosition;
+ public bool EnableExternalEvent;
+ public int MaxBufferCountCached;
+
+ public readonly object Lock = new object();
+
+ private KEvent _waitBufferFreeEvent;
+ private KEvent _frameAvailableEvent;
+
+ public ulong Owner { get; }
+
+ public bool Active { get; private set; }
+
+ public const int BufferHistoryArraySize = 8;
+
+ public event Action BufferQueued;
+
+ public BufferQueueCore(Switch device, ulong pid)
+ {
+ 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.KernelContext);
+ _frameAvailableEvent = new KEvent(device.System.KernelContext);
+
+ Owner = pid;
+
+ Active = true;
+
+ BufferHistory = new BufferInfo[BufferHistoryArraySize];
+ EnableExternalEvent = true;
+ MaxBufferCountCached = 0;
+ }
+
+ 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 void UpdateMaxBufferCountCachedLocked(int slot)
+ {
+ if (MaxBufferCountCached <= slot)
+ {
+ MaxBufferCountCached = slot + 1;
+ }
+ }
+
+ public int GetMaxBufferCountLocked(bool async)
+ {
+ int minMaxBufferCount = GetMinMaxBufferCountLocked(async);
+
+ int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount);
+
+ if (OverrideMaxBufferCount != 0)
+ {
+ return 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()
+ {
+ if (EnableExternalEvent)
+ {
+ _waitBufferFreeEvent.WritableEvent.Signal();
+ }
+ }
+
+ public void SignalFrameAvailableEvent()
+ {
+ if (EnableExternalEvent)
+ {
+ _frameAvailableEvent.WritableEvent.Signal();
+ }
+ }
+
+ public void PrepareForExit()
+ {
+ lock (Lock)
+ {
+ Active = false;
+
+ Monitor.PulseAll(Lock);
+ }
+ }
+
+ // 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()
+ {
+ WaitForLock();
+ }
+
+ public void SignalIsAllocatingEvent()
+ {
+ Monitor.PulseAll(Lock);
+ }
+
+ public void WaitIsAllocatingEvent()
+ {
+ WaitForLock();
+ }
+
+ public void SignalQueueEvent()
+ {
+ BufferQueued?.Invoke();
+ }
+
+ private void WaitForLock()
+ {
+ if (Active)
+ {
+ 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 (IsAllocating)
+ {
+ WaitIsAllocatingEvent();
+ }
+ }
+
+ public void CheckSystemEventsLocked(int maxBufferCount)
+ {
+ if (!EnableExternalEvent)
+ {
+ return;
+ }
+
+ 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.Error?.Print(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.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})");
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs
new file mode 100644
index 00000000..833bc26e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs
@@ -0,0 +1,871 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Settings;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ class BufferQueueProducer : IGraphicBufferProducer
+ {
+ public BufferQueueCore Core { get; }
+
+ private readonly ITickSource _tickSource;
+
+ private uint _stickyTransform;
+
+ private uint _nextCallbackTicket;
+ private uint _currentCallbackTicket;
+ private uint _callbackTicket;
+
+ private readonly object _callbackLock = new object();
+
+ public BufferQueueProducer(BufferQueueCore core, ITickSource tickSource)
+ {
+ Core = core;
+ _tickSource = tickSource;
+
+ _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;
+ }
+
+ int preallocatedBufferCount = GetPreallocatedBufferCountLocked();
+
+ if (preallocatedBufferCount <= 0)
+ {
+ Core.Queue.Clear();
+ Core.FreeAllBuffersLocked();
+ }
+ else if (preallocatedBufferCount < bufferCount)
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger, "Not enough buffers. Try with more pre-allocated buffers");
+
+ return Status.Success;
+ }
+
+ Core.OverrideMaxBufferCount = bufferCount;
+
+ Core.SignalDequeueEvent();
+ Core.SignalWaitBufferFreeEvent();
+
+ 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.Error?.Print(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;
+ }
+
+ 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].IsPreallocated)
+ {
+ slot = BufferSlotArray.InvalidBufferSlot;
+ fence = AndroidFence.NoFence;
+
+ return Status.NoMemory;
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger,
+ $"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}");
+
+ slot = BufferSlotArray.InvalidBufferSlot;
+ fence = AndroidFence.NoFence;
+
+ return Status.NoInit;
+ }
+ }
+
+ Core.Slots[slot].BufferState = BufferState.Dequeued;
+
+ Core.UpdateMaxBufferCountCachedLocked(slot);
+
+ fence = Core.Slots[slot].Fence;
+
+ Core.Slots[slot].Fence = AndroidFence.NoFence;
+ Core.Slots[slot].QueueTime = TimeSpanType.Zero;
+ Core.Slots[slot].PresentationTime = TimeSpanType.Zero;
+
+ 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.Error?.Print(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)
+ {
+ Core.WaitWhileAllocatingLocked();
+
+ Status status = WaitForFreeSlotThenRelock(false, out slot, out Status returnFlags);
+
+ if (status != Status.Success)
+ {
+ return status;
+ }
+
+ if (slot == BufferSlotArray.InvalidBufferSlot)
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger, "No available buffer slots");
+
+ return Status.Busy;
+ }
+
+ Core.UpdateMaxBufferCountCachedLocked(slot);
+
+ 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.Error?.Print(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;
+ Core.Slots[slot].QueueTime = TimeSpanType.FromTimeSpan(_tickSource.ElapsedTime);
+ Core.Slots[slot].PresentationTime = TimeSpanType.Zero;
+
+ 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);
+
+ Core.BufferHistoryPosition = (Core.BufferHistoryPosition + 1) % BufferQueueCore.BufferHistoryArraySize;
+
+ Core.BufferHistory[Core.BufferHistoryPosition] = new BufferInfo
+ {
+ FrameNumber = Core.FrameCounter,
+ QueueTime = Core.Slots[slot].QueueTime,
+ State = BufferState.Queued
+ };
+
+ _stickyTransform = input.StickyTransform;
+
+ if (Core.Queue.Count == 0)
+ {
+ 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
+ };
+
+ if ((input.StickyTransform & 8) != 0)
+ {
+ output.TransformHint |= NativeWindowTransform.ReturnFrameNumber;
+ output.FrameNumber = Core.Slots[slot].FrameNumber;
+ }
+
+ _callbackTicket = _nextCallbackTicket++;
+ }
+
+ lock (_callbackLock)
+ {
+ while (_callbackTicket != _currentCallbackTicket)
+ {
+ Monitor.Wait(_callbackLock);
+ }
+
+ frameAvailableListener?.OnFrameAvailable(ref item);
+ frameReplaceListener?.OnFrameReplaced(ref item);
+
+ _currentCallbackTicket++;
+
+ Monitor.PulseAll(_callbackLock);
+ }
+
+ Core.SignalQueueEvent();
+
+ 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();
+ Core.SignalWaitBufferFreeEvent();
+ }
+ }
+
+ 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.ConsumerRunningBehind:
+ outValue = Core.Queue.Count > 1 ? 1 : 0;
+ 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;
+
+ if (NxSettings.Settings.TryGetValue("nv!nvn_no_vsync_capability", out object noVSyncCapability) && (bool)noVSyncCapability)
+ {
+ output.TransformHint |= NativeWindowTransform.NoVSyncCapability;
+ }
+
+ return Status.Success;
+ default:
+ return Status.BadValue;
+ }
+ }
+ }
+
+ public override Status Disconnect(NativeWindowApi api)
+ {
+ IProducerListener producerListener = null;
+
+ Status status = Status.BadValue;
+
+ lock (Core.Lock)
+ {
+ Core.WaitWhileAllocatingLocked();
+
+ 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.Queue.Clear();
+ Core.FreeAllBuffersLocked();
+ Core.SignalDequeueEvent();
+
+ producerListener = Core.ProducerListener;
+
+ Core.ProducerListener = null;
+ Core.ConnectedApi = NativeWindowApi.NoApi;
+
+ Core.SignalWaitBufferFreeEvent();
+ Core.SignalFrameAvailableEvent();
+
+ status = Status.Success;
+ }
+ break;
+ }
+ }
+
+ producerListener?.OnBufferReleased();
+
+ return status;
+ }
+
+ private int GetPreallocatedBufferCountLocked()
+ {
+ int bufferCount = 0;
+
+ for (int i = 0; i < Core.Slots.Length; i++)
+ {
+ if (Core.Slots[i].IsPreallocated)
+ {
+ bufferCount++;
+ }
+ }
+
+ return bufferCount;
+ }
+
+ 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].AcquireCalled = false;
+ Core.Slots[slot].NeedsCleanupOnRelease = false;
+ Core.Slots[slot].IsPreallocated = !graphicBuffer.IsNull;
+ 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;
+ }
+
+ Core.OverrideMaxBufferCount = GetPreallocatedBufferCountLocked();
+ Core.UseAsyncBuffer = 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
+ {
+ bool allBufferFreed = true;
+
+ for (int i = 0; i < Core.Slots.Length; i++)
+ {
+ if (!Core.Slots[i].GraphicBuffer.IsNull)
+ {
+ allBufferFreed = false;
+ break;
+ }
+ }
+
+ if (allBufferFreed)
+ {
+ Core.Queue.Clear();
+ Core.FreeAllBuffersLocked();
+ Core.SignalDequeueEvent();
+ Core.SignalWaitBufferFreeEvent();
+ Core.SignalFrameAvailableEvent();
+
+ return Status.Success;
+ }
+ }
+
+ 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;
+ }
+
+
+ if (maxBufferCount < Core.MaxBufferCountCached)
+ {
+ for (int slot = maxBufferCount; slot < Core.MaxBufferCountCached; slot++)
+ {
+ if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull && !Core.Slots[slot].IsPreallocated)
+ {
+ 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.Error?.Print(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();
+
+ if (!Core.Active)
+ {
+ break;
+ }
+ }
+ }
+
+ return Status.Success;
+ }
+
+ protected override KReadableEvent GetWaitBufferFreeEvent()
+ {
+ return Core.GetWaitBufferFreeEvent();
+ }
+
+ public override Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos)
+ {
+ if (bufferHistoryCount <= 0)
+ {
+ bufferInfos = Span<BufferInfo>.Empty;
+
+ return Status.BadValue;
+ }
+
+ lock (Core.Lock)
+ {
+ bufferHistoryCount = Math.Min(bufferHistoryCount, Core.BufferHistory.Length);
+
+ BufferInfo[] result = new BufferInfo[bufferHistoryCount];
+
+ uint position = Core.BufferHistoryPosition;
+
+ for (uint i = 0; i < bufferHistoryCount; i++)
+ {
+ result[i] = Core.BufferHistory[(position - i) % Core.BufferHistory.Length];
+
+ position--;
+ }
+
+ bufferInfos = result;
+
+ return Status.Success;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
new file mode 100644
index 00000000..fb84934a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
@@ -0,0 +1,29 @@
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+
+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 TimeSpanType QueueTime;
+ public TimeSpanType PresentationTime;
+ public bool IsPreallocated;
+
+ public BufferSlot()
+ {
+ GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
+ BufferState = BufferState.Free;
+ QueueTime = TimeSpanType.Zero;
+ PresentationTime = TimeSpanType.Zero;
+ IsPreallocated = false;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs
new file mode 100644
index 00000000..d2404c58
--- /dev/null
+++ b/src/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;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs
new file mode 100644
index 00000000..49fceed9
--- /dev/null
+++ b/src/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;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs
new file mode 100644
index 00000000..d6c98be1
--- /dev/null
+++ b/src/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.Error?.Print(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.Error?.Print(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.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}");
+
+ return ResultCode.Success;
+ }
+
+ return binder.OnTransact(code, flags, inputParcel, outputParcel);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs
new file mode 100644
index 00000000..9003201b
--- /dev/null
+++ b/src/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.Error?.Print(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; }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs
new file mode 100644
index 00000000..78f9c2e7
--- /dev/null
+++ b/src/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();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs
new file mode 100644
index 00000000..bfb76952
--- /dev/null
+++ b/src/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);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs
new file mode 100644
index 00000000..f0b393a0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs
@@ -0,0 +1,304 @@
+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 ulong FrameNumber;
+
+ public void WriteToParcel(Parcel parcel)
+ {
+ parcel.WriteUInt32(Width);
+ parcel.WriteUInt32(Height);
+ parcel.WriteUnmanagedType(ref TransformHint);
+ parcel.WriteUInt32(NumPendingBuffers);
+
+ if (TransformHint.HasFlag(NativeWindowTransform.ReturnFrameNumber))
+ {
+ parcel.WriteUInt64(FrameNumber);
+ }
+ }
+ }
+
+ 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);
+
+ queueOutput.WriteToParcel(outputParcel);
+
+ 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);
+
+ queueOutput.WriteToParcel(outputParcel);
+
+ 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;
+ case TransactionCode.GetBufferHistory:
+ int bufferHistoryCount = inputParcel.ReadInt32();
+
+ status = GetBufferHistory(bufferHistoryCount, out Span<BufferInfo> bufferInfos);
+
+ outputParcel.WriteStatus(status);
+
+ outputParcel.WriteInt32(bufferInfos.Length);
+
+ outputParcel.WriteUnmanagedSpan<BufferInfo>(bufferInfos);
+
+ break;
+ default:
+ throw new NotImplementedException($"Transaction {(TransactionCode)code} not implemented");
+ }
+
+ if (status != Status.Success)
+ {
+ Logger.Error?.Print(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);
+
+ public abstract Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
new file mode 100644
index 00000000..42fc2761
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
@@ -0,0 +1,109 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Buffers;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ abstract class IHOSBinderDriver : IpcService
+ {
+ public IHOSBinderDriver() { }
+
+ [CommandCmif(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 = context.Request.SendBuff[0].Position;
+ ulong dataSize = context.Request.SendBuff[0].Size;
+
+ ulong replyPos = context.Request.ReceiveBuff[0].Position;
+ ulong replySize = context.Request.ReceiveBuff[0].Size;
+
+ ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
+
+ Span<byte> outputParcel = new Span<byte>(new byte[replySize]);
+
+ ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
+
+ if (result == ResultCode.Success)
+ {
+ context.Memory.Write(replyPos, outputParcel);
+ }
+
+ return result;
+ }
+
+ [CommandCmif(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);
+ }
+
+ [CommandCmif(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) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(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();
+
+ (ulong dataPos, ulong dataSize) = context.Request.GetBufferType0x21();
+ (ulong replyPos, ulong replySize) = context.Request.GetBufferType0x22();
+
+ ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
+
+ using (IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.Shared.RentCleared(replySize))
+ {
+ Span<byte> outputParcel = outputParcelOwner.Memory.Span;
+
+ ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
+
+ if (result == ResultCode.Success)
+ {
+ context.Memory.Write(replyPos, outputParcel);
+ }
+
+ 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);
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs
new file mode 100644
index 00000000..43d2101e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ interface IProducerListener
+ {
+ void OnBufferReleased();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs
new file mode 100644
index 00000000..5f014e13
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ enum LayerState
+ {
+ NotInitialized,
+ ManagedClosed,
+ ManagedOpened,
+ Stray
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs
new file mode 100644
index 00000000..1ae2732f
--- /dev/null
+++ b/src/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
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs
new file mode 100644
index 00000000..c40b4fa1
--- /dev/null
+++ b/src/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
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs
new file mode 100644
index 00000000..4194c915
--- /dev/null
+++ b/src/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,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs
new file mode 100644
index 00000000..66482b12
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs
@@ -0,0 +1,18 @@
+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,
+ NoVSyncCapability = 0x10,
+ ReturnFrameNumber = 0x20
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
new file mode 100644
index 00000000..19b22157
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
@@ -0,0 +1,221 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Utilities;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ class 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)
+ {
+ uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
+
+ _rawData = new byte[BitUtils.AlignUp<uint>(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)
+ {
+ return "";
+ }
+
+ 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
+ {
+ Type = 2,
+ Flags = 0,
+ BinderId = HOSBinderDriverServer.GetBinderId(obj),
+ };
+
+ Encoding.ASCII.GetBytes(serviceName).CopyTo(flatBinderObject.ServiceName);
+
+ WriteUnmanagedType(ref flatBinderObject);
+
+ // TODO: figure out what this value is
+
+ WriteInplaceObject(new byte[4] { 0, 0, 0, 0 });
+ }
+
+ public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable
+ {
+ bool hasObject = ReadBoolean();
+
+ if (hasObject)
+ {
+ T obj = ReadFlattenable<T>();
+
+ return new AndroidStrongPointer<T>(obj);
+ }
+ else
+ {
+ return new AndroidStrongPointer<T>();
+ }
+ }
+
+ public void WriteStrongPointer<T>(ref AndroidStrongPointer<T> value) where T: unmanaged, IFlattenable
+ {
+ WriteBoolean(!value.IsNull);
+
+ if (!value.IsNull)
+ {
+ WriteFlattenable<T>(ref value.Object);
+ }
+ }
+
+ public void WriteFlattenable<T>(ref T value) where T : unmanaged, IFlattenable
+ {
+ WriteInt64(value.GetFlattenedSize());
+
+ value.Flatten(this);
+ }
+
+ 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 WriteUnmanagedSpan<T>(ReadOnlySpan<T> value) where T : unmanaged
+ {
+ WriteInplace(MemoryMarshal.Cast<T, byte>(value));
+ }
+
+ public void WriteUnmanagedType<T>(ref T value) where T : unmanaged
+ {
+ WriteInplace(SpanHelpers.AsByteSpan(ref value));
+ }
+
+ 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>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs
new file mode 100644
index 00000000..27068af2
--- /dev/null
+++ b/src/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;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs
new file mode 100644
index 00000000..c0ddea10
--- /dev/null
+++ b/src/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,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs
new file mode 100644
index 00000000..5a151902
--- /dev/null
+++ b/src/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,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
new file mode 100644
index 00000000..c7cddf10
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
@@ -0,0 +1,548 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ using ResultCode = Ryujinx.HLE.HOS.Services.Vi.ResultCode;
+
+ 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 ManualResetEvent _event = new ManualResetEvent(false);
+ private AutoResetEvent _nextFrameEvent = new AutoResetEvent(true);
+ private long _ticks;
+ private long _ticksPerFrame;
+ private long _spinTicks;
+ private long _1msTicks;
+
+ private int _swapInterval;
+ private int _swapIntervalDelay;
+
+ private readonly object Lock = new object();
+
+ public long RenderLayerId { get; private set; }
+
+ private class Layer
+ {
+ public int ProducerBinderId;
+ public IGraphicBufferProducer Producer;
+ public BufferItemConsumer Consumer;
+ public BufferQueueCore Core;
+ public ulong Owner;
+ public LayerState State;
+ }
+
+ private class TextureCallbackInformation
+ {
+ public Layer Layer;
+ public BufferItem Item;
+ }
+
+ public SurfaceFlinger(Switch device)
+ {
+ _device = device;
+ _layers = new Dictionary<long, Layer>();
+ RenderLayerId = 0;
+
+ _composerThread = new Thread(HandleComposition)
+ {
+ Name = "SurfaceFlinger.Composer"
+ };
+
+ _chrono = new Stopwatch();
+ _chrono.Start();
+
+ _ticks = 0;
+ _spinTicks = Stopwatch.Frequency / 500;
+ _1msTicks = Stopwatch.Frequency / 1000;
+
+ UpdateSwapInterval(1);
+
+ _composerThread.Start();
+ }
+
+ private void UpdateSwapInterval(int swapInterval)
+ {
+ _swapInterval = swapInterval;
+
+ // If the swap interval is 0, Game VSync is disabled.
+ if (_swapInterval == 0)
+ {
+ _nextFrameEvent.Set();
+ _ticksPerFrame = 1;
+ }
+ else
+ {
+ _ticksPerFrame = Stopwatch.Frequency / TargetFps;
+ }
+ }
+
+ public IGraphicBufferProducer CreateLayer(out long layerId, ulong pid, LayerState initialState = LayerState.ManagedClosed)
+ {
+ layerId = 1;
+
+ lock (Lock)
+ {
+ foreach (KeyValuePair<long, Layer> pair in _layers)
+ {
+ if (pair.Key >= layerId)
+ {
+ layerId = pair.Key + 1;
+ }
+ }
+ }
+
+ CreateLayerFromId(pid, layerId, initialState);
+
+ return GetProducerByLayerId(layerId);
+ }
+
+ private void CreateLayerFromId(ulong pid, long layerId, LayerState initialState)
+ {
+ lock (Lock)
+ {
+ Logger.Info?.Print(LogClass.SurfaceFlinger, $"Creating layer {layerId}");
+
+ BufferQueueCore core = BufferQueue.CreateBufferQueue(_device, pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer);
+
+ core.BufferQueued += () =>
+ {
+ _nextFrameEvent.Set();
+ };
+
+ _layers.Add(layerId, new Layer
+ {
+ ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer),
+ Producer = producer,
+ Consumer = new BufferItemConsumer(_device, consumer, 0, -1, false, this),
+ Core = core,
+ Owner = pid,
+ State = initialState
+ });
+ }
+ }
+
+ public ResultCode OpenLayer(ulong pid, long layerId, out IBinder producer)
+ {
+ Layer layer = GetLayerByIdLocked(layerId);
+
+ if (layer == null || layer.State != LayerState.ManagedClosed)
+ {
+ producer = null;
+
+ return ResultCode.InvalidArguments;
+ }
+
+ layer.State = LayerState.ManagedOpened;
+ producer = layer.Producer;
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode CloseLayer(long layerId)
+ {
+ lock (Lock)
+ {
+ Layer layer = GetLayerByIdLocked(layerId);
+
+ if (layer == null)
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to close layer {layerId}");
+
+ return ResultCode.InvalidValue;
+ }
+
+ CloseLayer(layerId, layer);
+
+ return ResultCode.Success;
+ }
+ }
+
+ public ResultCode DestroyManagedLayer(long layerId)
+ {
+ lock (Lock)
+ {
+ Layer layer = GetLayerByIdLocked(layerId);
+
+ if (layer == null)
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (not found)");
+
+ return ResultCode.InvalidValue;
+ }
+
+ if (layer.State != LayerState.ManagedClosed && layer.State != LayerState.ManagedOpened)
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (permission denied)");
+
+ return ResultCode.PermissionDenied;
+ }
+
+ HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
+
+ if (_layers.Remove(layerId) && layer.State == LayerState.ManagedOpened)
+ {
+ CloseLayer(layerId, layer);
+ }
+
+ return ResultCode.Success;
+ }
+ }
+
+ public ResultCode DestroyStrayLayer(long layerId)
+ {
+ lock (Lock)
+ {
+ Layer layer = GetLayerByIdLocked(layerId);
+
+ if (layer == null)
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (not found)");
+
+ return ResultCode.InvalidValue;
+ }
+
+ if (layer.State != LayerState.Stray)
+ {
+ Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (permission denied)");
+
+ return ResultCode.PermissionDenied;
+ }
+
+ HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
+
+ if (_layers.Remove(layerId))
+ {
+ CloseLayer(layerId, layer);
+ }
+
+ return ResultCode.Success;
+ }
+ }
+
+ private void CloseLayer(long layerId, Layer layer)
+ {
+ // If the layer was removed and the current in use, we need to change the current layer in use.
+ if (RenderLayerId == layerId)
+ {
+ // If no layer is availaible, reset to default value.
+ if (_layers.Count == 0)
+ {
+ SetRenderLayer(0);
+ }
+ else
+ {
+ SetRenderLayer(_layers.Last().Key);
+ }
+ }
+
+ if (layer.State == LayerState.ManagedOpened)
+ {
+ layer.State = LayerState.ManagedClosed;
+ }
+ }
+
+ public void SetRenderLayer(long layerId)
+ {
+ lock (Lock)
+ {
+ RenderLayerId = 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;
+
+ long lastTicks = _chrono.ElapsedTicks;
+
+ while (_isRunning)
+ {
+ long ticks = _chrono.ElapsedTicks;
+
+ if (_swapInterval == 0)
+ {
+ Compose();
+
+ _device.System?.SignalVsync();
+
+ _nextFrameEvent.WaitOne(17);
+ lastTicks = ticks;
+ }
+ else
+ {
+ _ticks += ticks - lastTicks;
+ lastTicks = ticks;
+
+ if (_ticks >= _ticksPerFrame)
+ {
+ if (_swapIntervalDelay-- == 0)
+ {
+ Compose();
+
+ // When a frame is presented, delay the next one by its swap interval value.
+ _swapIntervalDelay = Math.Max(0, _swapInterval - 1);
+ }
+
+ _device.System?.SignalVsync();
+
+ // Apply a maximum bound of 3 frames to the tick remainder, in case some event causes Ryujinx to pause for a long time or messes with the timer.
+ _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame * 3);
+ }
+
+ // Sleep if possible. If the time til the next frame is too low, spin wait instead.
+ long diff = _ticksPerFrame - (_ticks + _chrono.ElapsedTicks - ticks);
+ if (diff > 0)
+ {
+ if (diff < _spinTicks)
+ {
+ do
+ {
+ // SpinWait is a little more HT/SMT friendly than aggressively updating/checking ticks.
+ // The value of 5 still gives us quite a bit of precision (~0.0003ms variance at worst) while waiting a reasonable amount of time.
+ Thread.SpinWait(5);
+
+ ticks = _chrono.ElapsedTicks;
+ _ticks += ticks - lastTicks;
+ lastTicks = ticks;
+ } while (_ticks < _ticksPerFrame);
+ }
+ else
+ {
+ _event.WaitOne((int)(diff / _1msTicks));
+ }
+ }
+ }
+ }
+ }
+
+ public void Compose()
+ {
+ lock (Lock)
+ {
+ // TODO: support multilayers (& multidisplay ?)
+ if (RenderLayerId == 0)
+ {
+ return;
+ }
+
+ Layer layer = GetLayerByIdLocked(RenderLayerId);
+
+ 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;
+ }
+
+ ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
+
+ NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle);
+
+ ulong frameBufferAddress = 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);
+
+ AspectRatio aspectRatio = _device.Configuration.AspectRatio;
+ bool isStretched = aspectRatio == AspectRatio.Stretched;
+
+ ImageCrop crop = new ImageCrop(
+ cropRect.Left,
+ cropRect.Right,
+ cropRect.Top,
+ cropRect.Bottom,
+ flipX,
+ flipY,
+ isStretched,
+ aspectRatio.ToFloatX(),
+ aspectRatio.ToFloatY());
+
+ TextureCallbackInformation textureCallbackInformation = new TextureCallbackInformation
+ {
+ Layer = layer,
+ Item = item
+ };
+
+ if (_device.Gpu.Window.EnqueueFrameThreadSafe(
+ layer.Owner,
+ frameBufferAddress,
+ frameBufferWidth,
+ frameBufferHeight,
+ 0,
+ false,
+ gobBlocksInY,
+ format,
+ bytesPerPixel,
+ crop,
+ AcquireBuffer,
+ ReleaseBuffer,
+ textureCallbackInformation))
+ {
+ if (item.Fence.FenceCount == 0)
+ {
+ _device.Gpu.Window.SignalFrameReady();
+ _device.Gpu.GPFifo.Interrupt();
+ }
+ else
+ {
+ item.Fence.RegisterCallback(_device.Gpu, (x) =>
+ {
+ _device.Gpu.Window.SignalFrameReady();
+ _device.Gpu.GPFifo.Interrupt();
+ });
+ }
+ }
+ else
+ {
+ ReleaseBuffer(textureCallbackInformation);
+ }
+ }
+
+ private void ReleaseBuffer(object obj)
+ {
+ ReleaseBuffer((TextureCallbackInformation)obj);
+ }
+
+ private void ReleaseBuffer(TextureCallbackInformation information)
+ {
+ AndroidFence fence = AndroidFence.NoFence;
+
+ information.Layer.Consumer.ReleaseBuffer(information.Item, ref 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;
+
+ foreach (Layer layer in _layers.Values)
+ {
+ layer.Core.PrepareForExit();
+ }
+ }
+
+ public void OnFrameAvailable(ref BufferItem item)
+ {
+ _device.Statistics.RecordGameFrameTime();
+ }
+
+ public void OnFrameReplaced(ref BufferItem item)
+ {
+ _device.Statistics.RecordGameFrameTime();
+ }
+
+ public void OnBuffersReleased() {}
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs
new file mode 100644
index 00000000..5b72e257
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs
@@ -0,0 +1,104 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.Graphics.Gpu.Synchronization;
+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.Error?.Print(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 void RegisterCallback(GpuContext gpuContext, Action<SyncpointWaiterHandle> callback)
+ {
+ ref NvFence fence = ref NvFences[FenceCount - 1];
+
+ if (fence.IsValid())
+ {
+ gpuContext.Synchronization.RegisterCallbackOnSyncpoint(fence.Id, fence.Value, callback);
+ }
+ else
+ {
+ callback(null);
+ }
+ }
+
+ 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>();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs
new file mode 100644
index 00000000..c356671b
--- /dev/null
+++ b/src/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;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs
new file mode 100644
index 00000000..12c41b0d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x1C, Pack = 1)]
+ struct BufferInfo
+ {
+ public ulong FrameNumber;
+ public TimeSpanType QueueTime;
+ public TimeSpanType PresentationTime;
+ public BufferState State;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs
new file mode 100644
index 00000000..19fc7900
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/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;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs
new file mode 100644
index 00000000..1787f5a6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ internal enum BufferState
+ {
+ Free = 0,
+ Dequeued = 1,
+ Queued = 2,
+ Acquired = 3
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs
new file mode 100644
index 00000000..b47d35b4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ enum ColorBytePerPixel
+ {
+ Bpp1 = 1,
+ Bpp2 = 2,
+ Bpp4 = 4,
+ Bpp8 = 8,
+ Bpp16 = 16,
+ Bpp24 = 24,
+ Bpp32 = 32,
+ Bpp48 = 48,
+ Bpp64 = 64,
+ Bpp96 = 96,
+ Bpp128 = 128
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs
new file mode 100644
index 00000000..e9669f12
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs
@@ -0,0 +1,42 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ enum ColorComponent : uint
+ {
+ X1 = (0x01 << ColorShift.Component) | ColorBytePerPixel.Bpp1,
+ X2 = (0x02 << ColorShift.Component) | ColorBytePerPixel.Bpp2,
+ X4 = (0x03 << ColorShift.Component) | ColorBytePerPixel.Bpp4,
+ X8 = (0x04 << ColorShift.Component) | ColorBytePerPixel.Bpp8,
+ Y4X4 = (0x05 << ColorShift.Component) | ColorBytePerPixel.Bpp8,
+ X3Y3Z2 = (0x06 << ColorShift.Component) | ColorBytePerPixel.Bpp8,
+ X8Y8 = (0x07 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X8Y8X8Z8 = (0x08 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ Y8X8Z8X8 = (0x09 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X16 = (0x0A << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ Y2X14 = (0x0B << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ Y4X12 = (0x0C << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ Y6X10 = (0x0D << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ Y8X8 = (0x0E << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X10 = (0x0F << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X12 = (0x10 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ Z5Y5X6 = (0x11 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X5Y6Z5 = (0x12 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X6Y5Z5 = (0x13 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X1Y5Z5W5 = (0x14 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X4Y4Z4W4 = (0x15 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X5Y1Z5W5 = (0x16 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X5Y5Z1W5 = (0x17 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X5Y5Z5W1 = (0x18 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
+ X8Y8Z8 = (0x19 << ColorShift.Component) | ColorBytePerPixel.Bpp24,
+ X24 = (0x1A << ColorShift.Component) | ColorBytePerPixel.Bpp24,
+ X32 = (0x1C << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ X16Y16 = (0x1D << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ X11Y11Z10 = (0x1E << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ X2Y10Z10W10 = (0x20 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ X8Y8Z8W8 = (0x21 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ Y10X10 = (0x22 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ X10Y10Z10W2 = (0x23 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ Y12X12 = (0x24 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
+ X20Y20Z20 = (0x26 << ColorShift.Component) | ColorBytePerPixel.Bpp64,
+ X16Y16Z16W16 = (0x27 << ColorShift.Component) | ColorBytePerPixel.Bpp64,
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs
new file mode 100644
index 00000000..cfa3b018
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ enum ColorDataType
+ {
+ Integer = 0x0 << ColorShift.DataType,
+ Float = 0x1 << ColorShift.DataType,
+ Stencil = 0x2 << ColorShift.DataType
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs
new file mode 100644
index 00000000..227d648a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs
@@ -0,0 +1,235 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ enum ColorFormat : ulong
+ {
+ NonColor8 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ NonColor16 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
+ NonColor24 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X24 | ColorDataType.Integer,
+ NonColor32 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X32 | ColorDataType.Integer,
+ X4C4 = ColorSpace.NonColor | ColorSwizzle.Y000 | ColorComponent.Y4X4 | ColorDataType.Integer,
+ A4L4 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y4X4 | ColorDataType.Integer,
+ A8L8 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y8X8 | ColorDataType.Integer,
+ Float_A16L16 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.X16Y16 | ColorDataType.Float,
+ A1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
+ A4B4G4R4 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
+ A5B5G5R1 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
+ A2B10G10R10 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ A8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ Float_A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
+ A1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
+ A4R4G4B4 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
+ A5R1G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X5Y1Z5W5 | ColorDataType.Integer,
+ A2R10G10B10 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ A8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A1 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X1 | ColorDataType.Integer,
+ A2 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X2 | ColorDataType.Integer,
+ A4 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X4 | ColorDataType.Integer,
+ A8 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X8 | ColorDataType.Integer,
+ A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Integer,
+ A32 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X32 | ColorDataType.Integer,
+ Float_A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Float,
+ L4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y4X4 | ColorDataType.Integer,
+ L8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y8X8 | ColorDataType.Integer,
+ B4G4R4A4 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
+ B5G5R1A5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z1W5 | ColorDataType.Integer,
+ B5G5R5A1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
+ B8G8R8A8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ B10G10R10A2 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ R1G5B5A5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
+ R4G4B4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
+ R5G5B5A1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
+ R8G8B8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ R10G10B10A2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ L1 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X1 | ColorDataType.Integer,
+ L2 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X2 | ColorDataType.Integer,
+ L4 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X4 | ColorDataType.Integer,
+ L8 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X8 | ColorDataType.Integer,
+ L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Integer,
+ L32 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X32 | ColorDataType.Integer,
+ Float_L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Float,
+ B5G6R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer,
+ B6G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X6Y5Z5 | ColorDataType.Integer,
+ B5G5R5X1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
+ B8_G8_R8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer,
+ B8G8R8X8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ Float_B10G11R11 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X11Y11Z10 | ColorDataType.Float,
+ X1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
+ X8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ Float_X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
+ R3G3B2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X3Y3Z2 | ColorDataType.Integer,
+ R5G5B6 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.Z5Y5X6 | ColorDataType.Integer,
+ R5G6B5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer,
+ R5G5B5X1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
+ R8_G8_B8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer,
+ R8G8B8X8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ X1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
+ X8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ RG8 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Integer,
+ Float_R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Float,
+ R8 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X8 | ColorDataType.Integer,
+ R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Integer,
+ Float_R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Float,
+ A2B10G10R10_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ A8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ A2R10G10B10_sRGB = ColorSpace.SRGB | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ B10G10R10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ R10G10B10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ X8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ X16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ A2B10G10R10_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ A8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ A2R10G10B10_709 = ColorSpace.RGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ B10G10R10A2_709 = ColorSpace.RGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ R10G10B10A2_709 = ColorSpace.RGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ X8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ X16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ A2B10G10R10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ A8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ A2R10G10B10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ B10G10R10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ R10G10B10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ X8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ X16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ Float_A16B16G16R16_scRGB_Linear = ColorSpace.LinearScRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
+ A2B10G10R10_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ A8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ A2R10G10B10_2020 = ColorSpace.RGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ B10G10R10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ R10G10B10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ X8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ X16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ A2B10G10R10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ A8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ Float_A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
+ A2R10G10B10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
+ B10G10R10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ R10G10B10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
+ X8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ X16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ Float_A16B16G16R16_2020_PQ = ColorSpace.RGB2020_PQ | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
+ A4I4 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y4X4 | ColorDataType.Integer,
+ A8I8 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y8X8 | ColorDataType.Integer,
+ I4A4 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y4X4 | ColorDataType.Integer,
+ I8A8 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y8X8 | ColorDataType.Integer,
+ I1 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X1 | ColorDataType.Integer,
+ I2 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X2 | ColorDataType.Integer,
+ I4 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X4 | ColorDataType.Integer,
+ I8 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ A8Y8U8V8 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ A16Y16U16V16 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
+ Y8U8V8A8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
+ V8_U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ V8U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ V10U10 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ V12U12 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ V8 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
+ V10 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
+ V12 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
+ U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ U8V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ U10V10 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ U12V12 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ U8 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
+ U10 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
+ U12 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
+ Y8 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ Y10 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
+ Y12 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
+ YVYU = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer,
+ VYUY = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer,
+ UYVY = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer,
+ YUYV = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer,
+ Y8_U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer,
+ V8_U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ V8U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
+ U8_V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ U8V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
+ Y8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ V8_U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ V8U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
+ U8_V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ U8V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
+ Y8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ V8_U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ V8U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ V10U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ V12U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
+ V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
+ V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
+ U8_V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ U8V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ U10V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ U12V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
+ U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
+ U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
+ Y8_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ Y10_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
+ Y12_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
+ V8_U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ V8U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ V10U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ V12U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
+ V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
+ V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
+ U8_V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
+ U8V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
+ U10V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ U12V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
+ U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
+ U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
+ Y8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ Y10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
+ Y12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
+ V10U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ V12U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
+ V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
+ U10V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
+ U12V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
+ U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
+ U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
+ Y10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
+ Y12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
+ Bayer8RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ Bayer16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
+ BayerS16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
+ X2Bayer14RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
+ X4Bayer12RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
+ X6Bayer10RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
+ Bayer8BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ Bayer16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
+ BayerS16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
+ X2Bayer14BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
+ X4Bayer12BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
+ X6Bayer10BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
+ Bayer8GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ Bayer16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
+ BayerS16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
+ X2Bayer14GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
+ X4Bayer12GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
+ X6Bayer10GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
+ Bayer8GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
+ Bayer16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
+ BayerS16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
+ X2Bayer14GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
+ X4Bayer12GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
+ X6Bayer10GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
+ XYZ = ColorSpace.XYZ | ColorSwizzle.XYZ1 | ColorComponent.X20Y20Z20 | ColorDataType.Float,
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs
new file mode 100644
index 00000000..3ad216a8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ class ColorShift
+ {
+ public const int Swizzle = 16;
+ public const int DataType = 14;
+ public const int Space = 32;
+ public const int Component = 8;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs
new file mode 100644
index 00000000..9003a00b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ enum ColorSpace : ulong
+ {
+ NonColor = 0x0L << ColorShift.Space,
+ LinearRGBA = 0x1L << ColorShift.Space,
+ SRGB = 0x2L << ColorShift.Space,
+
+ RGB709 = 0x3L << ColorShift.Space,
+ LinearRGB709 = 0x4L << ColorShift.Space,
+
+ LinearScRGB = 0x5L << ColorShift.Space,
+
+ RGB2020 = 0x6L << ColorShift.Space,
+ LinearRGB2020 = 0x7L << ColorShift.Space,
+ RGB2020_PQ = 0x8L << ColorShift.Space,
+
+ ColorIndex = 0x9L << ColorShift.Space,
+
+ YCbCr601 = 0xAL << ColorShift.Space,
+ YCbCr601_RR = 0xBL << ColorShift.Space,
+ YCbCr601_ER = 0xCL << ColorShift.Space,
+ YCbCr709 = 0xDL << ColorShift.Space,
+ YCbCr709_ER = 0xEL << ColorShift.Space,
+
+ BayerRGGB = 0x10L << ColorShift.Space,
+ BayerBGGR = 0x11L << ColorShift.Space,
+ BayerGRBG = 0x12L << ColorShift.Space,
+ BayerGBRG = 0x13L << ColorShift.Space,
+
+ XYZ = 0x14L << ColorShift.Space,
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs
new file mode 100644
index 00000000..4c1370c7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs
@@ -0,0 +1,31 @@
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ enum ColorSwizzle
+ {
+ XYZW = 0x688 << ColorShift.Swizzle,
+ ZYXW = 0x60a << ColorShift.Swizzle,
+ WZYX = 0x053 << ColorShift.Swizzle,
+ YZWX = 0x0d1 << ColorShift.Swizzle,
+ XYZ1 = 0xa88 << ColorShift.Swizzle,
+ YZW1 = 0xad1 << ColorShift.Swizzle,
+ XXX1 = 0xa00 << ColorShift.Swizzle,
+ XZY1 = 0xa50 << ColorShift.Swizzle,
+ ZYX1 = 0xa0a << ColorShift.Swizzle,
+ WZY1 = 0xa53 << ColorShift.Swizzle,
+ X000 = 0x920 << ColorShift.Swizzle,
+ Y000 = 0x921 << ColorShift.Swizzle,
+ XY01 = 0xb08 << ColorShift.Swizzle,
+ X001 = 0xb20 << ColorShift.Swizzle,
+ X00X = 0x121 << ColorShift.Swizzle,
+ X00Y = 0x320 << ColorShift.Swizzle,
+ _0YX0 = 0x80c << ColorShift.Swizzle,
+ _0ZY0 = 0x814 << ColorShift.Swizzle,
+ _0XZ0 = 0x884 << ColorShift.Swizzle,
+ _0X00 = 0x904 << ColorShift.Swizzle,
+ _00X0 = 0x824 << ColorShift.Swizzle,
+ _000X = 0x124 << ColorShift.Swizzle,
+ _0XY0 = 0x844 << ColorShift.Swizzle,
+ XXXY = 0x200 << ColorShift.Swizzle,
+ YYYX = 0x049 << ColorShift.Swizzle
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs
new file mode 100644
index 00000000..d1143225
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs
@@ -0,0 +1,74 @@
+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(ulong pid)
+ {
+ NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.NvMapId);
+
+ for (int i = 0; i < Buffer.Surfaces.Length; i++)
+ {
+ NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle);
+ }
+ }
+
+ public void DecrementNvMapHandleRefCount(ulong pid)
+ {
+ NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.NvMapId);
+
+ for (int i = 0; i < Buffer.Surfaces.Length; i++)
+ {
+ NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle);
+ }
+ }
+
+ public uint GetFlattenedSize()
+ {
+ return (uint)Unsafe.SizeOf<GraphicBuffer>();
+ }
+
+ public uint GetFdCount()
+ {
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs
new file mode 100644
index 00000000..77495922
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs
@@ -0,0 +1,21 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 1)]
+ struct GraphicBufferHeader
+ {
+ public int Magic;
+ public int Width;
+ public int Height;
+ public int Stride;
+ public PixelFormat Format;
+ public int Usage;
+
+ public int Pid;
+ public int RefCount;
+
+ public int FdsCount;
+ public int IntsCount;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs
new file mode 100644
index 00000000..6bb47dcc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs
@@ -0,0 +1,41 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ [StructLayout(LayoutKind.Explicit, Size = 0x144, Pack = 1)]
+ struct NvGraphicBuffer
+ {
+ [FieldOffset(0x4)]
+ public int NvMapId;
+
+ [FieldOffset(0xC)]
+ public int Magic;
+
+ [FieldOffset(0x10)]
+ public int Pid;
+
+ [FieldOffset(0x14)]
+ public int Type;
+
+ [FieldOffset(0x18)]
+ public int Usage;
+
+ [FieldOffset(0x1C)]
+ public int PixelFormat;
+
+ [FieldOffset(0x20)]
+ public int ExternalPixelFormat;
+
+ [FieldOffset(0x24)]
+ public int Stride;
+
+ [FieldOffset(0x28)]
+ public int FrameBufferSize;
+
+ [FieldOffset(0x2C)]
+ public int PlanesCount;
+
+ [FieldOffset(0x34)]
+ public NvGraphicBufferSurfaceArray Surfaces;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs
new file mode 100644
index 00000000..e084bc73
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs
@@ -0,0 +1,44 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ [StructLayout(LayoutKind.Explicit, Size = 0x58)]
+ struct NvGraphicBufferSurface
+ {
+ [FieldOffset(0)]
+ public uint Width;
+
+ [FieldOffset(0x4)]
+ public uint Height;
+
+ [FieldOffset(0x8)]
+ public ColorFormat ColorFormat;
+
+ [FieldOffset(0x10)]
+ public int Layout;
+
+ [FieldOffset(0x14)]
+ public int Pitch;
+
+ [FieldOffset(0x18)]
+ public int NvMapHandle;
+
+ [FieldOffset(0x1C)]
+ public int Offset;
+
+ [FieldOffset(0x20)]
+ public int Kind;
+
+ [FieldOffset(0x24)]
+ public int BlockHeightLog2;
+
+ [FieldOffset(0x28)]
+ public int ScanFormat;
+
+ [FieldOffset(0x30)]
+ public long Flags;
+
+ [FieldOffset(0x38)]
+ public long Size;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs
new file mode 100644
index 00000000..51ac98f8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ [StructLayout(LayoutKind.Explicit)]
+ struct NvGraphicBufferSurfaceArray
+ {
+ [FieldOffset(0x0)]
+ private NvGraphicBufferSurface Surface0;
+
+ [FieldOffset(0x58)]
+ private NvGraphicBufferSurface Surface1;
+
+ [FieldOffset(0xb0)]
+ private NvGraphicBufferSurface Surface2;
+
+ public NvGraphicBufferSurface this[int index]
+ {
+ get
+ {
+ if (index == 0)
+ {
+ return Surface0;
+ }
+ else if (index == 1)
+ {
+ return Surface1;
+ }
+ else if (index == 2)
+ {
+ return Surface2;
+ }
+
+ throw new IndexOutOfRangeException();
+ }
+ }
+
+ public int Length => 3;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs
new file mode 100644
index 00000000..a5dec969
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10)]
+ struct Rect : IEquatable<Rect>
+ {
+ 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);
+ }
+} \ No newline at end of file