diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs index 2aa601464b..ab83bdc2e1 100644 --- a/Ryujinx.Common/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -46,6 +46,7 @@ namespace Ryujinx.Common.Logging ServiceSsl, ServiceSss, ServiceTime, - ServiceVi + ServiceVi, + SurfaceFlinger } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs b/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs index 18f614bbeb..2c9f383a49 100644 --- a/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs +++ b/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs @@ -1,4 +1,5 @@ -using System; +using Ryujinx.Common.Logging; +using System; using System.Threading; namespace Ryujinx.Graphics.Gpu.Synchronization @@ -111,6 +112,16 @@ namespace Ryujinx.Graphics.Gpu.Synchronization throw new ArgumentOutOfRangeException(nameof(id)); } + bool warnAboutTimeout = false; + + // TODO: Remove this when GPU channel scheduling will be implemented. + if (timeout == Timeout.InfiniteTimeSpan) + { + timeout = TimeSpan.FromSeconds(1); + + warnAboutTimeout = true; + } + using (ManualResetEvent waitEvent = new ManualResetEvent(false)) { var info = _syncpoints[id].RegisterCallback(threshold, () => waitEvent.Set()); @@ -124,6 +135,11 @@ namespace Ryujinx.Graphics.Gpu.Synchronization if (!signaled && info != null) { + if (warnAboutTimeout) + { + Logger.PrintError(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms, resuming execution..."); + } + _syncpoints[id].UnregisterCallback(info); } diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 7f7fd4c6b7..d9dfb4b2ae 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -66,6 +66,8 @@ namespace Ryujinx.HLE.HOS internal Switch Device { get; private set; } + internal SurfaceFlinger SurfaceFlinger { get; private set; } + public SystemStateMgr State { get; private set; } internal bool KernelInitialized { get; private set; } @@ -268,6 +270,8 @@ namespace Ryujinx.HLE.HOS DatabaseImpl.Instance.InitializeDatabase(device); HostSyncpoint = new NvHostSyncpt(device); + + SurfaceFlinger = new SurfaceFlinger(device); } public void LoadCart(string exeFsDir, string romFsFile = null) @@ -850,6 +854,8 @@ namespace Ryujinx.HLE.HOS { _isDisposed = true; + SurfaceFlinger.Dispose(); + KProcess terminationProcess = new KProcess(this); KThread terminationThread = new KThread(this); @@ -873,12 +879,6 @@ namespace Ryujinx.HLE.HOS terminationThread.Start(); - // Signal the vsync event to avoid issues of KThread waiting on it. - if (Device.EnableDeviceVsync) - { - Device.VsyncEvent.Set(); - } - // Destroy nvservices channels as KThread could be waiting on some user events. // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade. INvDrvServices.Destroy(); diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs index f5c44e1ff2..ff56fbf5ff 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs @@ -8,6 +8,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl { class NvHostSyncpt { + public const int VBlank0SyncpointId = 26; + public const int VBlank1SyncpointId = 27; + private int[] _counterMin; private int[] _counterMax; private bool[] _clientManaged; @@ -24,6 +27,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl _counterMax = new int[SynchronizationManager.MaxHardwareSyncpoints]; _clientManaged = new bool[SynchronizationManager.MaxHardwareSyncpoints]; _assigned = new bool[SynchronizationManager.MaxHardwareSyncpoints]; + + // Reserve VBLANK syncpoints + ReserveSyncpointLocked(VBlank0SyncpointId, true); + ReserveSyncpointLocked(VBlank1SyncpointId, true); } private void ReserveSyncpointLocked(uint id, bool isClientManaged) diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs index d03ede9445..bbc3077936 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs @@ -165,12 +165,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap return NvInternalResult.InvalidInput; } - if (map.DecrementRefCount() <= 0) + if (DecrementMapRefCount(Owner, arguments.Handle)) { - DeleteMapWithHandle(arguments.Handle); - - Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {arguments.Handle}!"); - arguments.Address = map.Address; arguments.Flags = 0; } @@ -248,9 +244,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap return dict.Add(map); } - private bool DeleteMapWithHandle(int handle) + private static bool DeleteMapWithHandle(KProcess process, int handle) { - if (_maps.TryGetValue(Owner, out IdDictionary dict)) + if (_maps.TryGetValue(process, out IdDictionary dict)) { return dict.Delete(handle) != null; } @@ -258,6 +254,34 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap return false; } + public static void IncrementMapRefCount(KProcess process, int handle, bool allowHandleZero = false) + { + GetMapFromHandle(process, handle, allowHandleZero)?.IncrementRefCount(); + } + + public static bool DecrementMapRefCount(KProcess process, int handle) + { + NvMapHandle map = GetMapFromHandle(process, handle, false); + + if (map == null) + { + return false; + } + + if (map.DecrementRefCount() <= 0) + { + DeleteMapWithHandle(process, handle); + + Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {handle}!"); + + return true; + } + else + { + return false; + } + } + public static NvMapHandle GetMapFromHandle(KProcess process, int handle, bool allowHandleZero = false) { if ((allowHandleZero || handle != 0) && _maps.TryGetValue(process, out IdDictionary dict)) diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs index 98f1ea72f9..664610a43c 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs @@ -23,6 +23,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.Types Value = hostSyncpt.ReadSyncpointValue(Id); } + public void Increment(GpuContext gpuContext) + { + Value = gpuContext.Synchronization.IncrementSyncpoint(Id); + } + public bool Wait(GpuContext gpuContext, TimeSpan timeout) { if (IsValid()) diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItem.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItem.cs new file mode 100644 index 0000000000..19fc79008c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItem.cs @@ -0,0 +1,62 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferItem : ICloneable + { + public AndroidStrongPointer GraphicBuffer; + 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(); + 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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs new file mode 100644 index 0000000000..3b33bf8bc0 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs new file mode 100644 index 0000000000..3dd21fde9b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueue + { + public static void CreateBufferQueue(Switch device, KProcess process, out BufferQueueProducer producer, out BufferQueueConsumer consumer) + { + BufferQueueCore core = new BufferQueueCore(device, process); + + producer = new BufferQueueProducer(core); + consumer = new BufferQueueConsumer(core); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs new file mode 100644 index 0000000000..54ba56702b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs @@ -0,0 +1,372 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueConsumer + { + public BufferQueueCore Core { get; } + + public BufferQueueConsumer(BufferQueueCore core) + { + Core = core; + } + + public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent) + { + lock (Core.Lock) + { + int numAcquiredBuffers = 0; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (Core.Slots[i].BufferState == BufferState.Acquired) + { + numAcquiredBuffers++; + } + } + + if (numAcquiredBuffers >= Core.MaxAcquiredBufferCount + 1) + { + bufferItem = null; + + Logger.PrintDebug(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})"); + + return Status.InvalidOperation; + } + + if (Core.Queue.Count == 0) + { + bufferItem = null; + + return Status.NoBufferAvailaible; + } + + if (expectedPresent != 0) + { + // TODO: support this for advanced presenting. + throw new NotImplementedException(); + } + + bufferItem = Core.Queue[0]; + + if (Core.StillTracking(ref bufferItem)) + { + Core.Slots[bufferItem.Slot].AcquireCalled = true; + Core.Slots[bufferItem.Slot].NeedsCleanupOnRelease = true; + Core.Slots[bufferItem.Slot].BufferState = BufferState.Acquired; + Core.Slots[bufferItem.Slot].Fence = AndroidFence.NoFence; + } + + if (bufferItem.AcquireCalled) + { + bufferItem.GraphicBuffer.Reset(); + } + + Core.Queue.RemoveAt(0); + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true)); + Core.SignalDequeueEvent(); + } + + return Status.Success; + } + + public Status DetachBuffer(int slot) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByConsumerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer"); + + return Status.BadValue; + } + + Core.FreeBufferLocked(slot); + Core.SignalDequeueEvent(); + + return Status.Success; + } + } + + public Status AttachBuffer(out int slot, ref AndroidStrongPointer graphicBuffer) + { + lock (Core.Lock) + { + int numAcquiredBuffers = 0; + + int freeSlot = BufferSlotArray.InvalidBufferSlot; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (Core.Slots[i].BufferState == BufferState.Acquired) + { + numAcquiredBuffers++; + } + else if (Core.Slots[i].BufferState == BufferState.Free) + { + if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[i].FrameNumber < Core.Slots[freeSlot].FrameNumber) + { + freeSlot = i; + } + } + } + + if (numAcquiredBuffers > Core.MaxAcquiredBufferCount + 1) + { + slot = BufferSlotArray.InvalidBufferSlot; + + Logger.PrintError(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})"); + + return Status.InvalidOperation; + } + + if (freeSlot == BufferSlotArray.InvalidBufferSlot) + { + slot = BufferSlotArray.InvalidBufferSlot; + + return Status.NoMemory; + } + + slot = freeSlot; + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + Core.Slots[slot].BufferState = BufferState.Acquired; + Core.Slots[slot].AttachedByConsumer = true; + Core.Slots[slot].NeedsCleanupOnRelease = false; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].FrameNumber = 0; + Core.Slots[slot].AcquireCalled = false; + } + + return Status.Success; + } + + public Status ReleaseBuffer(int slot, ulong frameNumber, ref AndroidFence fence) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + IProducerListener listener = null; + + lock (Core.Lock) + { + if (Core.Slots[slot].FrameNumber != frameNumber) + { + return Status.StaleBufferSlot; + } + + foreach (BufferItem item in Core.Queue) + { + if (item.Slot == slot) + { + return Status.BadValue; + } + } + + if (Core.Slots[slot].BufferState == BufferState.Acquired) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].Fence = fence; + + listener = Core.ProducerListener; + } + else if (Core.Slots[slot].NeedsCleanupOnRelease) + { + Core.Slots[slot].NeedsCleanupOnRelease = false; + + return Status.StaleBufferSlot; + } + else + { + return Status.BadValue; + } + + Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner); + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true)); + Core.SignalDequeueEvent(); + } + + listener?.OnBufferReleased(); + + return Status.Success; + } + + public Status Connect(IConsumerListener consumerListener, bool controlledByApp) + { + if (consumerListener == null) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + Core.ConsumerListener = consumerListener; + Core.ConsumerControlledByApp = controlledByApp; + } + + return Status.Success; + } + + public Status Disconnect() + { + lock (Core.Lock) + { + if (!Core.IsConsumerConnectedLocked()) + { + return Status.BadValue; + } + + Core.IsAbandoned = true; + Core.ConsumerListener = null; + + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + } + + return Status.Success; + } + + public Status GetReleasedBuffers(out ulong slotMask) + { + slotMask = 0; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.BadValue; + } + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (!Core.Slots[slot].AcquireCalled) + { + slotMask |= 1UL << slot; + } + } + + for (int i = 0; i < Core.Queue.Count; i++) + { + if (Core.Queue[i].AcquireCalled) + { + slotMask &= ~(1UL << i); + } + } + } + + return Status.Success; + } + + public Status SetDefaultBufferSize(uint width, uint height) + { + if (width == 0 || height == 0) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + Core.DefaultWidth = (int)width; + Core.DefaultHeight = (int)height; + } + + return Status.Success; + } + + public Status SetDefaultMaxBufferCount(int bufferMaxCount) + { + lock (Core.Lock) + { + return Core.SetDefaultMaxBufferCountLocked(bufferMaxCount); + } + } + + public Status DisableAsyncBuffer() + { + lock (Core.Lock) + { + if (Core.IsConsumerConnectedLocked()) + { + return Status.InvalidOperation; + } + + Core.UseAsyncBuffer = false; + } + + return Status.Success; + } + + public Status SetMaxAcquiredBufferCount(int maxAcquiredBufferCount) + { + if (maxAcquiredBufferCount < 0 || maxAcquiredBufferCount > BufferSlotArray.MaxAcquiredBuffers) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.IsProducerConnectedLocked()) + { + return Status.InvalidOperation; + } + + Core.MaxAcquiredBufferCount = maxAcquiredBufferCount; + } + + return Status.Success; + } + + public Status SetDefaultBufferFormat(PixelFormat defaultFormat) + { + lock (Core.Lock) + { + Core.DefaultBufferFormat = defaultFormat; + } + + return Status.Success; + } + + public Status SetConsumerUsageBits(uint usage) + { + lock (Core.Lock) + { + Core.ConsumerUsageBits = usage; + } + + return Status.Success; + } + + public Status SetTransformHint(NativeWindowTransform transformHint) + { + lock (Core.Lock) + { + Core.TransformHint = transformHint; + } + + return Status.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs new file mode 100644 index 0000000000..8a5f583117 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs @@ -0,0 +1,283 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueCore + { + public BufferSlotArray Slots; + public int OverrideMaxBufferCount; + public bool UseAsyncBuffer; + public bool DequeueBufferCannotBlock; + public PixelFormat DefaultBufferFormat; + public int DefaultWidth; + public int DefaultHeight; + public int DefaultMaxBufferCount; + public int MaxAcquiredBufferCount; + public bool BufferHasBeenQueued; + public ulong FrameCounter; + public NativeWindowTransform TransformHint; + public bool IsAbandoned; + public NativeWindowApi ConnectedApi; + public bool IsAllocating; + public IProducerListener ProducerListener; + public IConsumerListener ConsumerListener; + public bool ConsumerControlledByApp; + public uint ConsumerUsageBits; + public List Queue; + + public readonly object Lock = new object(); + + private KEvent _waitBufferFreeEvent; + private KEvent _frameAvailableEvent; + + public KProcess Owner { get; } + + public BufferQueueCore(Switch device, KProcess process) + { + Slots = new BufferSlotArray(); + IsAbandoned = false; + OverrideMaxBufferCount = 0; + DequeueBufferCannotBlock = false; + UseAsyncBuffer = false; + DefaultWidth = 1; + DefaultHeight = 1; + DefaultMaxBufferCount = 2; + MaxAcquiredBufferCount = 1; + FrameCounter = 0; + TransformHint = 0; + DefaultBufferFormat = PixelFormat.Rgba8888; + IsAllocating = false; + ProducerListener = null; + ConsumerListener = null; + ConsumerUsageBits = 0; + + Queue = new List(); + + // TODO: CreateGraphicBufferAlloc? + + _waitBufferFreeEvent = new KEvent(device.System); + _frameAvailableEvent = new KEvent(device.System); + + Owner = process; + } + + public int GetMinUndequeuedBufferCountLocked(bool async) + { + if (!UseAsyncBuffer) + { + return 0; + } + + if (DequeueBufferCannotBlock || async) + { + return MaxAcquiredBufferCount + 1; + } + + return MaxAcquiredBufferCount; + } + + public int GetMinMaxBufferCountLocked(bool async) + { + return GetMinUndequeuedBufferCountLocked(async); + } + + public int GetMaxBufferCountLocked(bool async) + { + int minMaxBufferCount = GetMinMaxBufferCountLocked(async); + + int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount); + + if (OverrideMaxBufferCount != 0) + { + maxBufferCount = OverrideMaxBufferCount; + } + + // Preserve all buffers already in control of the producer and the consumer. + for (int slot = maxBufferCount; slot < Slots.Length; slot++) + { + BufferState state = Slots[slot].BufferState; + + if (state == BufferState.Queued || state == BufferState.Dequeued) + { + maxBufferCount = slot + 1; + } + } + + return maxBufferCount; + } + + public Status SetDefaultMaxBufferCountLocked(int count) + { + int minBufferCount = UseAsyncBuffer ? 2 : 1; + + if (count < minBufferCount || count > Slots.Length) + { + return Status.BadValue; + } + + DefaultMaxBufferCount = count; + + SignalDequeueEvent(); + + return Status.Success; + } + + public void SignalWaitBufferFreeEvent() + { + _waitBufferFreeEvent.WritableEvent.Signal(); + } + + public void SignalFrameAvailableEvent() + { + _frameAvailableEvent.WritableEvent.Signal(); + } + + // TODO: Find an accurate way to handle a regular condvar here as this will wake up unwanted threads in some edge cases. + public void SignalDequeueEvent() + { + Monitor.PulseAll(Lock); + } + + public void WaitDequeueEvent() + { + Monitor.Wait(Lock); + } + + public void SignalIsAbandonedEvent() + { + Monitor.PulseAll(Lock); + } + + public void WaitIsAbandonedEvent() + { + Monitor.Wait(Lock); + } + + public void FreeBufferLocked(int slot) + { + Slots[slot].GraphicBuffer.Reset(); + + if (Slots[slot].BufferState == BufferState.Acquired) + { + Slots[slot].NeedsCleanupOnRelease = true; + } + + Slots[slot].BufferState = BufferState.Free; + Slots[slot].FrameNumber = uint.MaxValue; + Slots[slot].AcquireCalled = false; + Slots[slot].Fence.FenceCount = 0; + } + + public void FreeAllBuffersLocked() + { + BufferHasBeenQueued = false; + + for (int slot = 0; slot < Slots.Length; slot++) + { + FreeBufferLocked(slot); + } + } + + public bool StillTracking(ref BufferItem item) + { + BufferSlot slot = Slots[item.Slot]; + + // TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be. + return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + } + + public void WaitWhileAllocatingLocked() + { + while (IsAbandoned) + { + WaitIsAbandonedEvent(); + } + } + + public void CheckSystemEventsLocked(int maxBufferCount) + { + bool needBufferReleaseSignal = false; + bool needFrameAvailableSignal = false; + + if (maxBufferCount > 1) + { + for (int i = 0; i < maxBufferCount; i++) + { + if (Slots[i].BufferState == BufferState.Queued) + { + needFrameAvailableSignal = true; + } + else if (Slots[i].BufferState == BufferState.Free) + { + needBufferReleaseSignal = true; + } + } + } + + if (needBufferReleaseSignal) + { + SignalWaitBufferFreeEvent(); + } + else + { + _waitBufferFreeEvent.WritableEvent.Clear(); + } + + if (needFrameAvailableSignal) + { + SignalFrameAvailableEvent(); + } + else + { + _frameAvailableEvent.WritableEvent.Clear(); + } + } + + public bool IsProducerConnectedLocked() + { + return ConnectedApi != NativeWindowApi.NoApi; + } + + public bool IsConsumerConnectedLocked() + { + return ConsumerListener != null; + } + + public KReadableEvent GetWaitBufferFreeEvent() + { + lock (Lock) + { + return _waitBufferFreeEvent.ReadableEvent; + } + } + + public bool IsOwnedByConsumerLocked(int slot) + { + if (Slots[slot].BufferState != BufferState.Acquired) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the consumer (state = {Slots[slot].BufferState})"); + + return false; + } + + return true; + } + + public bool IsOwnedByProducerLocked(int slot) + { + if (Slots[slot].BufferState != BufferState.Dequeued) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})"); + + return false; + } + + return true; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs new file mode 100644 index 0000000000..4c23eeec14 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs @@ -0,0 +1,752 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueProducer : IGraphicBufferProducer + { + public BufferQueueCore Core { get; } + + private uint _stickyTransform; + + private uint _nextCallbackTicket; + private uint _currentCallbackTicket; + private uint _callbackTicket; + + private readonly object _callbackLock = new object(); + + public BufferQueueProducer(BufferQueueCore core) + { + Core = core; + + _stickyTransform = 0; + _callbackTicket = 0; + _nextCallbackTicket = 0; + _currentCallbackTicket = 0; + } + + public override Status RequestBuffer(int slot, out AndroidStrongPointer graphicBuffer) + { + graphicBuffer = new AndroidStrongPointer(); + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + graphicBuffer.Set(Core.Slots[slot].GraphicBuffer); + + Core.Slots[slot].RequestBufferCalled = true; + + return Status.Success; + } + } + + public override Status SetBufferCount(int bufferCount) + { + IConsumerListener listener = null; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (bufferCount > BufferSlotArray.NumBufferSlots) + { + return Status.BadValue; + } + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (Core.Slots[slot].BufferState == BufferState.Dequeued) + { + return Status.BadValue; + } + } + + if (bufferCount == 0) + { + Core.OverrideMaxBufferCount = 0; + Core.SignalDequeueEvent(); + + return Status.Success; + } + + int minBufferSlots = Core.GetMinMaxBufferCountLocked(false); + + if (bufferCount < minBufferSlots) + { + return Status.BadValue; + } + + Core.OverrideMaxBufferCount = bufferCount; + Core.SignalDequeueEvent(); + + listener = Core.ConsumerListener; + } + + listener?.OnBuffersReleased(); + + return Status.Success; + } + + public override Status DequeueBuffer(out int slot, + out AndroidFence fence, + bool async, + uint width, + uint height, + PixelFormat format, + uint usage) + { + if ((width == 0 && height != 0) || (height == 0 && width != 0)) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.BadValue; + } + + Status returnFlags = Status.Success; + + bool attachedByConsumer = false; + + lock (Core.Lock) + { + if (format == PixelFormat.Unknown) + { + format = Core.DefaultBufferFormat; + } + + usage |= Core.ConsumerUsageBits; + + Status status = WaitForFreeSlotThenRelock(async, out slot, out returnFlags); + + if (status != Status.Success) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return status; + } + + if (slot == BufferSlotArray.InvalidBufferSlot) + { + fence = AndroidFence.NoFence; + + Logger.PrintError(LogClass.SurfaceFlinger, "No available buffer slots"); + + return Status.Busy; + } + + attachedByConsumer = Core.Slots[slot].AttachedByConsumer; + + if (width == 0 || height == 0) + { + width = (uint)Core.DefaultWidth; + height = (uint)Core.DefaultHeight; + } + + Core.Slots[slot].BufferState = BufferState.Dequeued; + + GraphicBuffer graphicBuffer = Core.Slots[slot].GraphicBuffer.Object; + + if (Core.Slots[slot].GraphicBuffer.IsNull + || graphicBuffer.Width != width + || graphicBuffer.Height != height + || graphicBuffer.Format != format + || (graphicBuffer.Usage & usage) != usage) + { + if (Core.Slots[slot].GraphicBuffer.IsNull) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.NoMemory; + } + else + { + string formattedError = $"Preallocated buffer mismatch - slot {slot}\n" + + $"available: Width = {graphicBuffer.Width} Height = {graphicBuffer.Height} Format = {graphicBuffer.Format} Usage = {graphicBuffer.Usage:x} " + + $"requested: Width = {width} Height = {height} Format = {format} Usage = {usage:x}"; + + Logger.PrintError(LogClass.SurfaceFlinger, formattedError); + + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.NoInit; + } + } + + fence = Core.Slots[slot].Fence; + + Core.Slots[slot].Fence = AndroidFence.NoFence; + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(async)); + } + + if (attachedByConsumer) + { + returnFlags |= Status.BufferNeedsReallocation; + } + + return returnFlags; + } + + public override Status DetachBuffer(int slot) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer"); + + return Status.BadValue; + } + + Core.FreeBufferLocked(slot); + Core.SignalDequeueEvent(); + + return Status.Success; + } + } + + public override Status DetachNextBuffer(out AndroidStrongPointer graphicBuffer, 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) + { + lock (Core.Lock) + { + Status status = WaitForFreeSlotThenRelock(false, out slot, out Status returnFlags); + + if (status != Status.Success) + { + return status; + } + + if (slot == BufferSlotArray.InvalidBufferSlot) + { + Logger.PrintError(LogClass.SurfaceFlinger, "No available buffer slots"); + + return Status.Busy; + } + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + Core.Slots[slot].BufferState = BufferState.Dequeued; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].RequestBufferCalled = true; + + return returnFlags; + } + } + + public override Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output) + { + output = default; + + switch (input.ScalingMode) + { + case NativeWindowScalingMode.Freeze: + case NativeWindowScalingMode.ScaleToWindow: + case NativeWindowScalingMode.ScaleCrop: + case NativeWindowScalingMode.Unknown: + case NativeWindowScalingMode.NoScaleCrop: + break; + default: + return Status.BadValue; + } + + BufferItem item = new BufferItem(); + + IConsumerListener frameAvailableListener = null; + IConsumerListener frameReplaceListener = null; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + int maxBufferCount = Core.GetMaxBufferCountLocked(input.Async != 0); + + if (input.Async != 0 && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount) + { + return Status.BadValue; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} was queued without requesting a buffer"); + + return Status.BadValue; + } + + input.Crop.Intersect(Core.Slots[slot].GraphicBuffer.Object.ToRect(), out Rect croppedRect); + + if (croppedRect != input.Crop) + { + return Status.BadValue; + } + + Core.Slots[slot].Fence = input.Fence; + Core.Slots[slot].BufferState = BufferState.Queued; + Core.FrameCounter++; + Core.Slots[slot].FrameNumber = Core.FrameCounter; + + item.AcquireCalled = Core.Slots[slot].AcquireCalled; + item.Crop = input.Crop; + item.Transform = input.Transform; + item.TransformToDisplayInverse = (input.Transform & NativeWindowTransform.InverseDisplay) == NativeWindowTransform.InverseDisplay; + item.ScalingMode = input.ScalingMode; + item.Timestamp = input.Timestamp; + item.IsAutoTimestamp = input.IsAutoTimestamp != 0; + item.SwapInterval = input.SwapInterval; + item.FrameNumber = Core.FrameCounter; + item.Slot = slot; + item.Fence = input.Fence; + item.IsDroppable = Core.DequeueBufferCannotBlock || input.Async != 0; + + item.GraphicBuffer.Set(Core.Slots[slot].GraphicBuffer); + item.GraphicBuffer.Object.IncrementNvMapHandleRefCount(Core.Owner); + + _stickyTransform = input.StickyTransform; + + if (Core.Queue.Count == 0) + { + Core.Queue.Add(item); + + frameAvailableListener = Core.ConsumerListener; + } + else + { + BufferItem frontItem = Core.Queue[0]; + + if (frontItem.IsDroppable) + { + if (Core.StillTracking(ref frontItem)) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].FrameNumber = 0; + } + + Core.Queue.RemoveAt(0); + Core.Queue.Insert(0, item); + + frameReplaceListener = Core.ConsumerListener; + } + else + { + Core.Queue.Add(item); + + frameAvailableListener = Core.ConsumerListener; + } + } + + Core.BufferHasBeenQueued = true; + Core.SignalDequeueEvent(); + + Core.CheckSystemEventsLocked(maxBufferCount); + + output = new QueueBufferOutput + { + Width = (uint)Core.DefaultWidth, + Height = (uint)Core.DefaultHeight, + TransformHint = Core.TransformHint, + NumPendingBuffers = (uint)Core.Queue.Count + }; + + _callbackTicket = _nextCallbackTicket++; + } + + lock (_callbackLock) + { + while (_callbackTicket != _currentCallbackTicket) + { + Monitor.Wait(_callbackLock); + } + + frameAvailableListener?.OnFrameAvailable(ref item); + frameReplaceListener?.OnFrameReplaced(ref item); + + _currentCallbackTicket++; + + Monitor.PulseAll(_callbackLock); + } + + return Status.Success; + } + + public override void CancelBuffer(int slot, ref AndroidFence fence) + { + lock (Core.Lock) + { + if (Core.IsAbandoned || slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return; + } + + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].FrameNumber = 0; + Core.Slots[slot].Fence = fence; + Core.SignalDequeueEvent(); + } + } + + public override Status Query(NativeWindowAttribute what, out int outValue) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + outValue = 0; + return Status.NoInit; + } + + switch (what) + { + case NativeWindowAttribute.Width: + outValue = Core.DefaultWidth; + return Status.Success; + case NativeWindowAttribute.Height: + outValue = Core.DefaultHeight; + return Status.Success; + case NativeWindowAttribute.Format: + outValue = (int)Core.DefaultBufferFormat; + return Status.Success; + case NativeWindowAttribute.MinUnqueuedBuffers: + outValue = Core.GetMinUndequeuedBufferCountLocked(false); + return Status.Success; + case NativeWindowAttribute.ConsumerUsageBits: + outValue = (int)Core.ConsumerUsageBits; + return Status.Success; + case NativeWindowAttribute.MaxBufferCountAsync: + outValue = Core.GetMaxBufferCountLocked(true); + return Status.Success; + default: + outValue = 0; + return Status.BadValue; + } + } + } + + public override Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output) + { + output = new QueueBufferOutput(); + + lock (Core.Lock) + { + if (Core.IsAbandoned || Core.ConsumerListener == null) + { + return Status.NoInit; + } + + if (Core.ConnectedApi != NativeWindowApi.NoApi) + { + return Status.BadValue; + } + + Core.BufferHasBeenQueued = false; + Core.DequeueBufferCannotBlock = Core.ConsumerControlledByApp && producerControlledByApp; + + switch (api) + { + case NativeWindowApi.NVN: + case NativeWindowApi.CPU: + case NativeWindowApi.Media: + case NativeWindowApi.Camera: + Core.ProducerListener = listener; + Core.ConnectedApi = api; + + output.Width = (uint)Core.DefaultWidth; + output.Height = (uint)Core.DefaultHeight; + output.TransformHint = Core.TransformHint; + output.NumPendingBuffers = (uint)Core.Queue.Count; + return Status.Success; + default: + return Status.BadValue; + } + } + } + + public override Status Disconnect(NativeWindowApi api) + { + IProducerListener producerListener = null; + + Status status = Status.BadValue; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.Success; + } + + switch (api) + { + case NativeWindowApi.NVN: + case NativeWindowApi.CPU: + case NativeWindowApi.Media: + case NativeWindowApi.Camera: + if (Core.ConnectedApi == api) + { + Core.FreeAllBuffersLocked(); + + producerListener = Core.ProducerListener; + + Core.ProducerListener = null; + Core.ConnectedApi = NativeWindowApi.NoApi; + + Core.SignalDequeueEvent(); + + status = Status.Success; + } + break; + } + } + + producerListener?.OnBufferReleased(); + + return status; + } + + public override Status SetPreallocatedBuffer(int slot, AndroidStrongPointer graphicBuffer) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].RequestBufferCalled = false; + Core.Slots[slot].NeedsCleanupOnRelease = false; + Core.Slots[slot].FrameNumber = 0; + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + if (!Core.Slots[slot].GraphicBuffer.IsNull) + { + Core.Slots[slot].GraphicBuffer.Object.Buffer.Usage &= (int)Core.ConsumerUsageBits; + } + + bool cleared = false; + + if (!graphicBuffer.IsNull) + { + // NOTE: Nintendo set the default width, height and format from the GraphicBuffer.. + // This is entirely wrong and should only be controlled by the consumer... + Core.DefaultWidth = graphicBuffer.Object.Width; + Core.DefaultHeight = graphicBuffer.Object.Height; + Core.DefaultBufferFormat = graphicBuffer.Object.Format; + } + else + { + foreach (BufferItem item in Core.Queue) + { + if (item.Slot >= BufferSlotArray.NumBufferSlots) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + Core.SignalFrameAvailableEvent(); + + cleared = true; + + break; + } + } + } + + // The dequeue event must not be signaled two times in case of clean up, + // but for some reason, it still signals the wait buffer free event two times... + if (!cleared) + { + Core.SignalDequeueEvent(); + } + + Core.SignalWaitBufferFreeEvent(); + + return Status.Success; + } + } + + private Status WaitForFreeSlotThenRelock(bool async, out int freeSlot, out Status returnStatus) + { + bool tryAgain = true; + + freeSlot = BufferSlotArray.InvalidBufferSlot; + returnStatus = Status.Success; + + while (tryAgain) + { + if (Core.IsAbandoned) + { + freeSlot = BufferSlotArray.InvalidBufferSlot; + + return Status.NoInit; + } + + int maxBufferCount = Core.GetMaxBufferCountLocked(async); + + if (async && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount) + { + freeSlot = BufferSlotArray.InvalidBufferSlot; + + return Status.BadValue; + } + + for (int slot = maxBufferCount; slot < Core.Slots.Length; slot++) + { + if (!Core.Slots[slot].GraphicBuffer.IsNull) + { + Core.FreeBufferLocked(slot); + returnStatus |= Status.ReleaseAllBuffers; + } + } + + freeSlot = BufferSlotArray.InvalidBufferSlot; + + int dequeuedCount = 0; + int acquiredCount = 0; + + for (int slot = 0; slot < maxBufferCount; slot++) + { + switch (Core.Slots[slot].BufferState) + { + case BufferState.Acquired: + acquiredCount++; + break; + case BufferState.Dequeued: + dequeuedCount++; + break; + case BufferState.Free: + if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[freeSlot].FrameNumber) + { + freeSlot = slot; + } + break; + default: + break; + } + } + + // The producer SHOULD call SetBufferCount otherwise it's not allowed to dequeue multiple buffers. + if (Core.OverrideMaxBufferCount == 0 && dequeuedCount > 0) + { + return Status.InvalidOperation; + } + + if (Core.BufferHasBeenQueued) + { + int newUndequeuedCount = maxBufferCount - (dequeuedCount + 1); + int minUndequeuedCount = Core.GetMinUndequeuedBufferCountLocked(async); + + if (newUndequeuedCount < minUndequeuedCount) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Min undequeued buffer count ({minUndequeuedCount}) exceeded (dequeued = {dequeuedCount} undequeued = {newUndequeuedCount})"); + + return Status.InvalidOperation; + } + } + + bool tooManyBuffers = Core.Queue.Count > maxBufferCount; + + tryAgain = freeSlot == BufferSlotArray.InvalidBufferSlot || tooManyBuffers; + + if (tryAgain) + { + if (async || (Core.DequeueBufferCannotBlock && acquiredCount < Core.MaxAcquiredBufferCount)) + { + Core.CheckSystemEventsLocked(maxBufferCount); + + return Status.WouldBlock; + } + + Core.WaitDequeueEvent(); + } + } + + return Status.Success; + } + + protected override KReadableEvent GetWaitBufferFreeEvent() + { + return Core.GetWaitBufferFreeEvent(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs new file mode 100644 index 0000000000..dbfba0ee8f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs @@ -0,0 +1,22 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferSlot + { + public AndroidStrongPointer GraphicBuffer; + public BufferState BufferState; + public bool RequestBufferCalled; + public ulong FrameNumber; + public AndroidFence Fence; + public bool AcquireCalled; + public bool NeedsCleanupOnRelease; + public bool AttachedByConsumer; + + public BufferSlot() + { + GraphicBuffer = new AndroidStrongPointer(); + BufferState = BufferState.Free; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs new file mode 100644 index 0000000000..d2404c587e --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs new file mode 100644 index 0000000000..49fceed9fa --- /dev/null +++ b/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; + public AndroidFence Fence; + public ulong FrameNumber; + + public Slot() + { + GraphicBuffer = new AndroidStrongPointer(); + } + } + + 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, 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) + { + 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) + { + 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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs new file mode 100644 index 0000000000..37bf5b5ca5 --- /dev/null +++ b/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 _registeredBinderObjects = new Dictionary(); + + 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 pair in _registeredBinderObjects) + { + if (ReferenceEquals(binder, pair.Value)) + { + return pair.Key; + } + } + + return -1; + } + } + + private static IBinder GetBinderObjectById(int binderId) + { + lock (_lock) + { + if (_registeredBinderObjects.TryGetValue(binderId, out IBinder binder)) + { + return binder; + } + + return null; + } + } + + protected override ResultCode AdjustRefcount(int binderId, int addVal, int type) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return ResultCode.Success; + } + + return binder.AdjustRefcount(addVal, type); + } + + protected override void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + readableEvent = null; + + Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return; + } + + binder.GetNativeHandle(typeId, out readableEvent); + } + + protected override ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan inputParcel, Span outputParcel) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return ResultCode.Success; + } + + return binder.OnTransact(code, flags, inputParcel, outputParcel); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs new file mode 100644 index 0000000000..b25ab28ed5 --- /dev/null +++ b/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 inputParcel, Span outputParcel) + { + Parcel inputParcelReader = new Parcel(inputParcel.ToArray()); + + // TODO: support objects? + Parcel outputParcelWriter = new Parcel((uint)(outputParcel.Length - Unsafe.SizeOf()), 0); + + string inputInterfaceToken = inputParcelReader.ReadInterfaceToken(); + + if (!InterfaceToken.Equals(inputInterfaceToken)) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Invalid interface token {inputInterfaceToken} (expected: {InterfaceToken}"); + + return ResultCode.Success; + } + + OnTransact(code, flags, inputParcelReader, outputParcelWriter); + + outputParcelWriter.Finish().CopyTo(outputParcel); + + return ResultCode.Success; + } + + void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel); + + string InterfaceToken { get; } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs new file mode 100644 index 0000000000..78f9c2e7e2 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs new file mode 100644 index 0000000000..bfb76952b1 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs new file mode 100644 index 0000000000..41e21648ea --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs @@ -0,0 +1,276 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + abstract class IGraphicBufferProducer : IBinder + { + public string InterfaceToken => "android.gui.IGraphicBufferProducer"; + + enum TransactionCode : uint + { + RequestBuffer = 1, + SetBufferCount, + DequeueBuffer, + DetachBuffer, + DetachNextBuffer, + AttachBuffer, + QueueBuffer, + CancelBuffer, + Query, + Connect, + Disconnect, + SetSidebandStream, + AllocateBuffers, + SetPreallocatedBuffer, + Reserved15, + GetBufferInfo, + GetBufferHistory + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x54)] + public struct QueueBufferInput : IFlattenable + { + public long Timestamp; + public int IsAutoTimestamp; + public Rect Crop; + public NativeWindowScalingMode ScalingMode; + public NativeWindowTransform Transform; + public uint StickyTransform; + public int Async; + public int SwapInterval; + public AndroidFence Fence; + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref this); + } + + public uint GetFdCount() + { + return 0; + } + + public uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf(); + } + + public void Unflatten(Parcel parcel) + { + this = parcel.ReadUnmanagedType(); + } + } + + public struct QueueBufferOutput + { + public uint Width; + public uint Height; + public NativeWindowTransform TransformHint; + public uint NumPendingBuffers; + } + + public ResultCode AdjustRefcount(int addVal, int type) + { + // TODO? + return ResultCode.Success; + } + + public void GetNativeHandle(uint typeId, out KReadableEvent readableEvent) + { + if (typeId == 0xF) + { + readableEvent = GetWaitBufferFreeEvent(); + } + else + { + throw new NotImplementedException($"Unimplemented native event type {typeId}!"); + } + } + + public void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel) + { + Status status = Status.Success; + int slot; + AndroidFence fence; + QueueBufferInput queueInput; + QueueBufferOutput queueOutput; + NativeWindowApi api; + + AndroidStrongPointer graphicBuffer; + AndroidStrongPointer 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(); + uint usage = inputParcel.ReadUInt32(); + + status = DequeueBuffer(out int dequeueSlot, out fence, async, width, height, format, usage); + strongFence = new AndroidStrongPointer(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(fence); + + outputParcel.WriteStrongPointer(ref graphicBuffer); + outputParcel.WriteStrongPointer(ref strongFence); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.AttachBuffer: + graphicBuffer = inputParcel.ReadStrongPointer(); + + status = AttachBuffer(out slot, graphicBuffer); + + outputParcel.WriteInt32(slot); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.QueueBuffer: + slot = inputParcel.ReadInt32(); + queueInput = inputParcel.ReadFlattenable(); + + status = QueueBuffer(slot, ref queueInput, out queueOutput); + + outputParcel.WriteUnmanagedType(ref queueOutput); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.CancelBuffer: + slot = inputParcel.ReadInt32(); + fence = inputParcel.ReadFlattenable(); + + CancelBuffer(slot, ref fence); + + outputParcel.WriteStatus(Status.Success); + + break; + case TransactionCode.Query: + NativeWindowAttribute what = inputParcel.ReadUnmanagedType(); + + 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(); + + bool producerControlledByApp = inputParcel.ReadBoolean(); + + status = Connect(listener, api, producerControlledByApp, out queueOutput); + + outputParcel.WriteUnmanagedType(ref queueOutput); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.Disconnect: + api = inputParcel.ReadUnmanagedType(); + + status = Disconnect(api); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.SetPreallocatedBuffer: + slot = inputParcel.ReadInt32(); + + graphicBuffer = inputParcel.ReadStrongPointer(); + + status = SetPreallocatedBuffer(slot, graphicBuffer); + + outputParcel.WriteStatus(status); + + break; + default: + throw new NotImplementedException($"Transaction {(TransactionCode)code} not implemented"); + } + + if (status != Status.Success) + { + Logger.PrintError(LogClass.SurfaceFlinger, $"Error returned by transaction {(TransactionCode)code}: {status}"); + } + } + + protected abstract KReadableEvent GetWaitBufferFreeEvent(); + + public abstract Status RequestBuffer(int slot, out AndroidStrongPointer graphicBuffer); + + 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, out AndroidFence fence); + + public abstract Status AttachBuffer(out int slot, AndroidStrongPointer 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); + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs new file mode 100644 index 0000000000..42924d38d3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs @@ -0,0 +1,104 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + abstract class IHOSBinderDriver : IpcService + { + public IHOSBinderDriver() {} + + [Command(0)] + // TransactParcel(s32, u32, u32, buffer) -> buffer + public ResultCode TransactParcel(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint code = context.RequestData.ReadUInt32(); + uint flags = context.RequestData.ReadUInt32(); + + ulong dataPos = (ulong)context.Request.SendBuff[0].Position; + ulong dataSize = (ulong)context.Request.SendBuff[0].Size; + + long replyPos = context.Request.ReceiveBuff[0].Position; + long replySize = context.Request.ReceiveBuff[0].Size; + + ReadOnlySpan inputParcel = context.Memory.GetSpan(dataPos, dataSize); + + Span outputParcel = new Span(new byte[replySize]); + + ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); + + if (result == ResultCode.Success) + { + context.Memory.WriteBytes(replyPos, outputParcel.ToArray()); + } + + return result; + } + + [Command(1)] + // AdjustRefcount(s32, s32, s32) + public ResultCode AdjustRefcount(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + int addVal = context.RequestData.ReadInt32(); + int type = context.RequestData.ReadInt32(); + + return AdjustRefcount(binderId, addVal, type); + } + + [Command(2)] + // GetNativeHandle(s32, s32) -> handle + public ResultCode GetNativeHandle(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint typeId = context.RequestData.ReadUInt32(); + + GetNativeHandle(binderId, typeId, out KReadableEvent readableEvent); + + if (context.Process.HandleTable.GenerateHandle(readableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [Command(3)] // 3.0.0+ + // TransactParcelAuto(s32, u32, u32, buffer) -> buffer + public ResultCode TransactParcelAuto(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint code = context.RequestData.ReadUInt32(); + uint flags = context.RequestData.ReadUInt32(); + + (long dataPos, long dataSize) = context.Request.GetBufferType0x21(); + (long replyPos, long replySize) = context.Request.GetBufferType0x22(); + + ReadOnlySpan inputParcel = context.Memory.GetSpan((ulong)dataPos, (ulong)dataSize); + + Span outputParcel = new Span(new byte[replySize]); + + ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); + + if (result == ResultCode.Success) + { + context.Memory.WriteBytes(replyPos, outputParcel.ToArray()); + } + + return result; + } + + protected abstract ResultCode AdjustRefcount(int binderId, int addVal, int type); + + protected abstract void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent); + + protected abstract ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan inputParcel, Span outputParcel); + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs new file mode 100644 index 0000000000..43d2101ef5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IProducerListener + { + void OnBufferReleased(); + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs new file mode 100644 index 0000000000..1ae2732f5e --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs new file mode 100644 index 0000000000..c40b4fa10b --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs new file mode 100644 index 0000000000..4194c91599 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs new file mode 100644 index 0000000000..650fe3c67a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [Flags] + enum NativeWindowTransform : uint + { + None = 0, + FlipX = 1, + FlipY = 2, + Rotate90 = 4, + Rotate180 = FlipX | FlipY, + Rotate270 = Rotate90 | Rotate180, + InverseDisplay = 8 + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs deleted file mode 100644 index 7b69f9cb26..0000000000 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs +++ /dev/null @@ -1,455 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; - -using static Ryujinx.HLE.HOS.Services.SurfaceFlinger.Parcel; - -namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger -{ - class NvFlinger : IDisposable - { - private delegate ResultCode ServiceProcessParcel(ServiceCtx context, BinaryReader parcelReader); - - private Dictionary<(string, int), ServiceProcessParcel> _commands; - - private KEvent _binderEvent; - - private IRenderer _renderer; - - private const int BufferQueueCount = 0x40; - private const int BufferQueueMask = BufferQueueCount - 1; - - private BufferEntry[] _bufferQueue; - - private AutoResetEvent _waitBufferFree; - - private bool _disposed; - - public NvFlinger(IRenderer renderer, KEvent binderEvent) - { - _commands = new Dictionary<(string, int), ServiceProcessParcel> - { - { ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x4), GbpDetachBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery }, - { ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect }, - { ("android.gui.IGraphicBufferProducer", 0xb), GbpDisconnect }, - { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer } - }; - - _renderer = renderer; - _binderEvent = binderEvent; - - _bufferQueue = new BufferEntry[0x40]; - - _waitBufferFree = new AutoResetEvent(false); - } - - public ResultCode ProcessParcelRequest(ServiceCtx context, byte[] parcelData, int code) - { - using (MemoryStream ms = new MemoryStream(parcelData)) - { - BinaryReader reader = new BinaryReader(ms); - - ms.Seek(4, SeekOrigin.Current); - - int strSize = reader.ReadInt32(); - - string interfaceName = Encoding.Unicode.GetString(reader.ReadBytes(strSize * 2)); - - long remainder = ms.Position & 0xf; - - if (remainder != 0) - { - ms.Seek(0x10 - remainder, SeekOrigin.Current); - } - - ms.Seek(0x50, SeekOrigin.Begin); - - if (_commands.TryGetValue((interfaceName, code), out ServiceProcessParcel procReq)) - { - Logger.PrintDebug(LogClass.ServiceVi, $"{interfaceName} {procReq.Method.Name}"); - - return procReq(context, reader); - } - else - { - throw new NotImplementedException($"{interfaceName} {code}"); - } - } - } - - private ResultCode GbpRequestBuffer(ServiceCtx context, BinaryReader parcelReader) - { - int slot = parcelReader.ReadInt32(); - - using (MemoryStream ms = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(ms); - - BufferEntry entry = _bufferQueue[slot]; - - int bufferCount = 1; //? - long bufferSize = entry.Data.Size; - - writer.Write(bufferCount); - writer.Write(bufferSize); - - entry.Data.Write(writer); - - writer.Write(0); - - return MakeReplyParcel(context, ms.ToArray()); - } - } - - private ResultCode GbpDequeueBuffer(ServiceCtx context, BinaryReader parcelReader) - { - // TODO: Errors. - int async = parcelReader.ReadInt32(); - int width = parcelReader.ReadInt32(); - int height = parcelReader.ReadInt32(); - int format = parcelReader.ReadInt32(); - int usage = parcelReader.ReadInt32(); - - int slot = GetFreeSlotBlocking(width, height); - - MultiFence multiFence = MultiFence.NoFence; - - using (MemoryStream ms = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(ms); - - // Allocated slot - writer.Write(slot); - - // Has multi fence - writer.Write(1); - - // Write the multi fnece - WriteFlattenedObject(writer, multiFence); - - // Padding - writer.Write(0); - - // Status - writer.Write(0); - - return MakeReplyParcel(context, ms.ToArray()); - } - } - - private ResultCode GbpQueueBuffer(ServiceCtx context, BinaryReader parcelReader) - { - context.Device.Statistics.RecordGameFrameTime(); - - // TODO: Errors. - int slot = parcelReader.ReadInt32(); - - long Position = parcelReader.BaseStream.Position; - - QueueBufferObject queueBufferObject = ReadFlattenedObject(parcelReader); - - parcelReader.BaseStream.Position = Position; - - _bufferQueue[slot].Transform = queueBufferObject.Transform; - _bufferQueue[slot].Fence = queueBufferObject.Fence; - _bufferQueue[slot].Crop = queueBufferObject.Crop; - _bufferQueue[slot].State = BufferState.Queued; - - SendFrameBuffer(context, slot); - - if (context.Device.EnableDeviceVsync) - { - context.Device.VsyncEvent.WaitOne(); - } - - return MakeReplyParcel(context, 1280, 720, 0, 0, 0); - } - - private ResultCode GbpDetachBuffer(ServiceCtx context, BinaryReader parcelReader) - { - return MakeReplyParcel(context, 0); - } - - private ResultCode GbpCancelBuffer(ServiceCtx context, BinaryReader parcelReader) - { - // TODO: Errors. - int slot = parcelReader.ReadInt32(); - - MultiFence fence = ReadFlattenedObject(parcelReader); - - _bufferQueue[slot].State = BufferState.Free; - - _waitBufferFree.Set(); - - return MakeReplyParcel(context, 0); - } - - private ResultCode GbpQuery(ServiceCtx context, BinaryReader parcelReader) - { - return MakeReplyParcel(context, 0, 0); - } - - private ResultCode GbpConnect(ServiceCtx context, BinaryReader parcelReader) - { - return MakeReplyParcel(context, 1280, 720, 0, 0, 0); - } - - private ResultCode GbpDisconnect(ServiceCtx context, BinaryReader parcelReader) - { - return MakeReplyParcel(context, 0); - } - - private ResultCode GbpPreallocBuffer(ServiceCtx context, BinaryReader parcelReader) - { - int slot = parcelReader.ReadInt32(); - - bool hasInput = parcelReader.ReadInt32() == 1; - - if (hasInput) - { - byte[] graphicBuffer = ReadFlattenedObject(parcelReader); - - _bufferQueue[slot].State = BufferState.Free; - - using (BinaryReader graphicBufferReader = new BinaryReader(new MemoryStream(graphicBuffer))) - { - _bufferQueue[slot].Data = new GbpBuffer(graphicBufferReader); - } - - } - - return MakeReplyParcel(context, 0); - } - - private byte[] ReadFlattenedObject(BinaryReader reader) - { - long flattenedObjectSize = reader.ReadInt64(); - - return reader.ReadBytes((int)flattenedObjectSize); - } - - private T ReadFlattenedObject(BinaryReader reader) where T: struct - { - long flattenedObjectSize = reader.ReadInt64(); - - Debug.Assert(flattenedObjectSize == Unsafe.SizeOf()); - - return reader.ReadStruct(); - } - - private unsafe void WriteFlattenedObject(BinaryWriter writer, T value) where T : struct - { - writer.Write(Unsafe.SizeOf()); - writer.WriteStruct(value); - } - - private ResultCode MakeReplyParcel(ServiceCtx context, params int[] ints) - { - using (MemoryStream ms = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(ms); - - foreach (int Int in ints) - { - writer.Write(Int); - } - - return MakeReplyParcel(context, ms.ToArray()); - } - } - - private ResultCode MakeReplyParcel(ServiceCtx context, byte[] data) - { - (long replyPos, long replySize) = context.Request.GetBufferType0x22(); - - byte[] reply = MakeParcel(data, new byte[0]); - - context.Memory.WriteBytes(replyPos, reply); - - return ResultCode.Success; - } - - private Format ConvertColorFormat(ColorFormat colorFormat) - { - switch (colorFormat) - { - case ColorFormat.A8B8G8R8: - return Format.R8G8B8A8Unorm; - case ColorFormat.X8B8G8R8: - return Format.R8G8B8A8Unorm; - case ColorFormat.R5G6B5: - return Format.B5G6R5Unorm; - case ColorFormat.A8R8G8B8: - return Format.B8G8R8A8Unorm; - case ColorFormat.A4B4G4R4: - return Format.R4G4B4A4Unorm; - default: - throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"); - } - } - - // TODO: support multi surface - private void SendFrameBuffer(ServiceCtx context, int slot) - { - int fbWidth = _bufferQueue[slot].Data.Header.Width; - int fbHeight = _bufferQueue[slot].Data.Header.Height; - - int nvMapHandle = _bufferQueue[slot].Data.Buffer.Surfaces[0].NvMapHandle; - - if (nvMapHandle == 0) - { - nvMapHandle = _bufferQueue[slot].Data.Buffer.NvMapId; - } - - int bufferOffset = _bufferQueue[slot].Data.Buffer.Surfaces[0].Offset; - - NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(context.Process, nvMapHandle); - - ulong fbAddr = (ulong)(map.Address + bufferOffset); - - _bufferQueue[slot].State = BufferState.Acquired; - - Format format = ConvertColorFormat(_bufferQueue[slot].Data.Buffer.Surfaces[0].ColorFormat); - - int bytesPerPixel = - format == Format.B5G6R5Unorm || - format == Format.R4G4B4A4Unorm ? 2 : 4; - - int gobBlocksInY = 1 << _bufferQueue[slot].Data.Buffer.Surfaces[0].BlockHeightLog2; - - // Note: Rotation is being ignored. - - Rect cropRect = _bufferQueue[slot].Crop; - - bool flipX = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipX); - bool flipY = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipY); - - ImageCrop crop = new ImageCrop( - cropRect.Left, - cropRect.Right, - cropRect.Top, - cropRect.Bottom, - flipX, - flipY); - - context.Device.Gpu.Window.EnqueueFrameThreadSafe( - fbAddr, - fbWidth, - fbHeight, - 0, - false, - gobBlocksInY, - format, - bytesPerPixel, - crop, - AcquireBuffer, - ReleaseBuffer, - slot); - } - - private void AcquireBuffer(GpuContext context, object slot) - { - AcquireBuffer(context, (int)slot); - } - - private void AcquireBuffer(GpuContext context, int slot) - { - _bufferQueue[slot].Fence.WaitForever(context); - } - - private void ReleaseBuffer(object slot) - { - ReleaseBuffer((int)slot); - } - - private void ReleaseBuffer(int slot) - { - _bufferQueue[slot].State = BufferState.Free; - - _binderEvent.ReadableEvent.Signal(); - - _waitBufferFree.Set(); - } - - private int GetFreeSlotBlocking(int width, int height) - { - int slot; - - do - { - if ((slot = GetFreeSlot(width, height)) != -1) - { - break; - } - - if (_disposed) - { - break; - } - - _waitBufferFree.WaitOne(); - } - while (!_disposed); - - return slot; - } - - private int GetFreeSlot(int width, int height) - { - lock (_bufferQueue) - { - for (int slot = 0; slot < _bufferQueue.Length; slot++) - { - if (_bufferQueue[slot].State != BufferState.Free) - { - continue; - } - - GbpBuffer data = _bufferQueue[slot].Data; - - if (data.Header.Width == width && - data.Header.Height == height) - { - _bufferQueue[slot].State = BufferState.Dequeued; - - return slot; - } - } - } - - return -1; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing && !_disposed) - { - _disposed = true; - - _waitBufferFree.Set(); - _waitBufferFree.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs index f5d934232e..3def026b87 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs @@ -1,58 +1,216 @@ +using Ryujinx.Common; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; using System; -using System.IO; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { - static class Parcel + class Parcel { - public static byte[] GetParcelData(byte[] parcel) + private readonly byte[] _rawData; + + private Span Raw => new Span(_rawData); + + private ref ParcelHeader Header => ref MemoryMarshal.Cast(_rawData)[0]; + + private Span Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize); + + private Span Objects => Raw.Slice((int)Header.ObjectOffset, (int)Header.ObjectsSize); + + private int _payloadPosition; + private int _objectPosition; + + public Parcel(byte[] rawData) { - if (parcel == null) + _rawData = rawData; + + _payloadPosition = 0; + _objectPosition = 0; + } + + public Parcel(uint payloadSize, uint objectsSize) + { + uint headerSize = (uint)Unsafe.SizeOf(); + + _rawData = new byte[BitUtils.AlignUp(headerSize + payloadSize + objectsSize, 4)]; + + Header.PayloadSize = payloadSize; + Header.ObjectsSize = objectsSize; + Header.PayloadOffset = headerSize; + Header.ObjectOffset = Header.PayloadOffset + Header.ObjectsSize; + } + + public string ReadInterfaceToken() + { + // Ignore the policy flags + int strictPolicy = ReadInt32(); + + return ReadString16(); + } + + public string ReadString16() + { + int size = ReadInt32(); + + if (size < 0) { - throw new ArgumentNullException(nameof(parcel)); + return ""; } - using (MemoryStream ms = new MemoryStream(parcel)) + ReadOnlySpan 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(); + public uint ReadUInt32() => ReadUnmanagedType(); + public bool ReadBoolean() => ReadUnmanagedType() != 0; + public long ReadInt64() => ReadUnmanagedType(); + public ulong ReadUInt64() => ReadUnmanagedType(); + + public T ReadFlattenable() where T : unmanaged, IFlattenable + { + long flattenableSize = ReadInt64(); + + T result = new T(); + + Debug.Assert(flattenableSize == result.GetFlattenedSize()); + + result.Unflatten(this); + + return result; + } + + public T ReadUnmanagedType() where T: unmanaged + { + ReadOnlySpan data = ReadInPlace(Unsafe.SizeOf()); + + return MemoryMarshal.Cast(data)[0]; + } + + public ReadOnlySpan ReadInPlace(int size) + { + ReadOnlySpan 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 ServiceName => MemoryMarshal.CreateSpan(ref _serviceNameStart, 0x8); + } + + public void WriteObject(T obj, string serviceName) where T: IBinder + { + FlatBinderObject flatBinderObject = new FlatBinderObject { - BinaryReader reader = new BinaryReader(ms); + Type = 2, + Flags = 0, + BinderId = HOSBinderDriverServer.GetBinderId(obj), + }; - int dataSize = reader.ReadInt32(); - int dataOffset = reader.ReadInt32(); - int objsSize = reader.ReadInt32(); - int objsOffset = reader.ReadInt32(); + Encoding.ASCII.GetBytes(serviceName).CopyTo(flatBinderObject.ServiceName); - ms.Seek(dataOffset - 0x10, SeekOrigin.Current); + WriteUnmanagedType(ref flatBinderObject); - return reader.ReadBytes(dataSize); + // TODO: figure out what this value is + + WriteInplaceObject(new byte[4] { 0, 0, 0, 0 }); + } + + public AndroidStrongPointer ReadStrongPointer() where T : unmanaged, IFlattenable + { + bool hasObject = ReadBoolean(); + + if (hasObject) + { + T obj = ReadFlattenable(); + + return new AndroidStrongPointer(obj); + } + else + { + return new AndroidStrongPointer(); } } - public static byte[] MakeParcel(byte[] data, byte[] objs) + public void WriteStrongPointer(ref AndroidStrongPointer value) where T: unmanaged, IFlattenable { - if (data == null) + WriteBoolean(!value.IsNull); + + if (!value.IsNull) { - throw new ArgumentNullException(nameof(data)); + WriteFlattenable(ref value.Object); } + } - if (objs == null) - { - throw new ArgumentNullException(nameof(objs)); - } + public void WriteFlattenable(ref T value) where T : unmanaged, IFlattenable + { + WriteInt64(value.GetFlattenedSize()); - using (MemoryStream ms = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(ms); + value.Flatten(this); + } - writer.Write(data.Length); - writer.Write(0x10); - writer.Write(objs.Length); - writer.Write(data.Length + 0x10); + 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); - writer.Write(data); - writer.Write(objs); + public void WriteUnmanagedType(ref T value) where T : unmanaged + { + WriteInplace(SpanHelpers.AsByteSpan(ref value)); + } - return ms.ToArray(); - } + public void WriteInplace(ReadOnlySpan data) + { + Span result = Payload.Slice(_payloadPosition, data.Length); + + data.CopyTo(result); + + _payloadPosition += BitUtils.AlignUp(data.Length, 4); + } + + public void WriteInplaceObject(ReadOnlySpan data) + { + Span result = Objects.Slice(_objectPosition, data.Length); + + data.CopyTo(result); + + _objectPosition += BitUtils.AlignUp(data.Length, 4); + } + + private void UpdateHeader() + { + uint headerSize = (uint)Unsafe.SizeOf(); + + Header.PayloadSize = (uint)_payloadPosition; + Header.ObjectsSize = (uint)_objectPosition; + Header.PayloadOffset = headerSize; + Header.ObjectOffset = Header.PayloadOffset + Header.PayloadSize; + } + + public ReadOnlySpan Finish() + { + UpdateHeader(); + + return Raw.Slice(0, (int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf())); } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs new file mode 100644 index 0000000000..27068af272 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs new file mode 100644 index 0000000000..c0ddea10b9 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs new file mode 100644 index 0000000000..5a1519021d --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs new file mode 100644 index 0000000000..12ebc7d7f3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs @@ -0,0 +1,376 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class SurfaceFlinger : IConsumerListener, IDisposable + { + private const int TargetFps = 60; + + private Switch _device; + + private Dictionary _layers; + + private bool _isRunning; + + private Thread _composerThread; + + private Stopwatch _chrono; + + private AndroidFence _vblankFence; + + private long _ticks; + private long _ticksPerFrame; + + private int _swapInterval; + + private readonly object Lock = new object(); + + public long LastId { get; private set; } + + private class Layer + { + public int ProducerBinderId; + public IGraphicBufferProducer Producer; + public BufferItemConsumer Consumer; + public KProcess Owner; + } + + private class TextureCallbackInformation + { + public Layer Layer; + public BufferItem Item; + public AndroidFence Fence; + } + + public SurfaceFlinger(Switch device) + { + _device = device; + _layers = new Dictionary(); + LastId = 0; + + _composerThread = new Thread(HandleComposition) + { + Name = "SurfaceFlinger.Composer" + }; + + _chrono = new Stopwatch(); + + _ticks = 0; + + UpdateSwapInterval(1); + + _vblankFence = AndroidFence.NoFence; + _vblankFence.AddFence(new NvFence + { + Id = NvHostSyncpt.VBlank0SyncpointId, + Value = 0 + }); + + _composerThread.Start(); + } + + private void UpdateSwapInterval(int swapInterval) + { + _swapInterval = swapInterval; + + // If the swap interval is 0, Game VSync is disabled. + if (_swapInterval == 0) + { + _ticksPerFrame = 1; + } + else + { + _ticksPerFrame = Stopwatch.Frequency / (TargetFps / _swapInterval); + } + } + + public IGraphicBufferProducer OpenLayer(KProcess process, long layerId) + { + bool needCreate; + + lock (Lock) + { + needCreate = GetLayerByIdLocked(layerId) == null; + } + + if (needCreate) + { + CreateLayerFromId(process, layerId); + } + + return GetProducerByLayerId(layerId); + } + + public IGraphicBufferProducer CreateLayer(KProcess process, out long layerId) + { + layerId = 1; + + lock (Lock) + { + foreach (KeyValuePair pair in _layers) + { + if (pair.Key >= layerId) + { + layerId = pair.Key + 1; + } + } + } + + CreateLayerFromId(process, layerId); + + return GetProducerByLayerId(layerId); + } + + private void CreateLayerFromId(KProcess process, long layerId) + { + lock (Lock) + { + Logger.PrintInfo(LogClass.SurfaceFlinger, $"Creating layer {layerId}"); + + BufferQueue.CreateBufferQueue(_device, process, out BufferQueueProducer producer, out BufferQueueConsumer consumer); + + _layers.Add(layerId, new Layer + { + ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer), + Producer = producer, + Consumer = new BufferItemConsumer(_device, consumer, 0, -1, false, this), + Owner = process + }); + + LastId = layerId; + } + } + + public bool CloseLayer(long layerId) + { + lock (Lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer != null) + { + HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId); + } + + return _layers.Remove(layerId); + } + } + + private Layer GetLayerByIdLocked(long layerId) + { + foreach (KeyValuePair pair in _layers) + { + if (pair.Key == layerId) + { + return pair.Value; + } + } + + return null; + } + + public IGraphicBufferProducer GetProducerByLayerId(long layerId) + { + lock (Lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer != null) + { + return layer.Producer; + } + } + + return null; + } + + private void HandleComposition() + { + _isRunning = true; + + while (_isRunning) + { + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (_ticks >= _ticksPerFrame) + { + Compose(); + + _device.System.SignalVsync(); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } + + // Sleep the minimal amount of time to avoid being too expensive. + Thread.Sleep(1); + } + } + + public void Compose() + { + lock (Lock) + { + _vblankFence.NvFences[0].Increment(_device.Gpu); + + // TODO: support multilayers (& multidisplay ?) + if (_layers.Count == 0) + { + return; + } + + Layer layer = GetLayerByIdLocked(LastId); + + Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0); + + if (acquireStatus == Status.Success) + { + // If device vsync is disabled, reflect the change. + if (!_device.EnableDeviceVsync) + { + if (_swapInterval != 0) + { + UpdateSwapInterval(0); + } + } + else if (item.SwapInterval != _swapInterval) + { + UpdateSwapInterval(item.SwapInterval); + } + + PostFrameBuffer(layer, item); + } + else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation) + { + throw new InvalidOperationException(); + } + } + } + + private void PostFrameBuffer(Layer layer, BufferItem item) + { + int frameBufferWidth = item.GraphicBuffer.Object.Width; + int frameBufferHeight = item.GraphicBuffer.Object.Height; + + int nvMapHandle = item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + + if (nvMapHandle == 0) + { + nvMapHandle = item.GraphicBuffer.Object.Buffer.NvMapId; + } + + int bufferOffset = item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle); + + ulong frameBufferAddress = (ulong)(map.Address + bufferOffset); + + Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat); + + int bytesPerPixel = + format == Format.B5G6R5Unorm || + format == Format.R4G4B4A4Unorm ? 2 : 4; + + int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2; + + // Note: Rotation is being ignored. + Rect cropRect = item.Crop; + + bool flipX = item.Transform.HasFlag(NativeWindowTransform.FlipX); + bool flipY = item.Transform.HasFlag(NativeWindowTransform.FlipY); + + ImageCrop crop = new ImageCrop( + cropRect.Left, + cropRect.Right, + cropRect.Top, + cropRect.Bottom, + flipX, + flipY); + + // Enforce that dequeueBuffer wait for the next vblank + _vblankFence.NvFences[0].Value++; + + TextureCallbackInformation textureCallbackInformation = new TextureCallbackInformation + { + Layer = layer, + Item = item, + Fence = _vblankFence + }; + + _device.Gpu.Window.EnqueueFrameThreadSafe( + frameBufferAddress, + frameBufferWidth, + frameBufferHeight, + 0, + false, + gobBlocksInY, + format, + bytesPerPixel, + crop, + AcquireBuffer, + ReleaseBuffer, + textureCallbackInformation); + } + + private void ReleaseBuffer(object obj) + { + ReleaseBuffer((TextureCallbackInformation)obj); + } + + private void ReleaseBuffer(TextureCallbackInformation information) + { + information.Layer.Consumer.ReleaseBuffer(information.Item, ref information.Fence); + } + + private void AcquireBuffer(GpuContext ignored, object obj) + { + AcquireBuffer((TextureCallbackInformation)obj); + } + + private void AcquireBuffer(TextureCallbackInformation information) + { + information.Item.Fence.WaitForever(_device.Gpu); + } + + public static Format ConvertColorFormat(ColorFormat colorFormat) + { + return colorFormat switch + { + ColorFormat.A8B8G8R8 => Format.R8G8B8A8Unorm, + ColorFormat.X8B8G8R8 => Format.R8G8B8A8Unorm, + ColorFormat.R5G6B5 => Format.B5G6R5Unorm, + ColorFormat.A8R8G8B8 => Format.B8G8R8A8Unorm, + ColorFormat.A4B4G4R4 => Format.R4G4B4A4Unorm, + _ => throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"), + }; + } + + public void Dispose() + { + _isRunning = false; + } + + public void OnFrameAvailable(ref BufferItem item) + { + _device.Statistics.RecordGameFrameTime(); + } + + public void OnFrameReplaced(ref BufferItem item) + { + _device.Statistics.RecordGameFrameTime(); + } + + public void OnBuffersReleased() {} + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs new file mode 100644 index 0000000000..a5a9908f25 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs @@ -0,0 +1,89 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)] + struct AndroidFence : IFlattenable + { + public int FenceCount; + + private byte _fenceStorageStart; + + private Span _storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf() * 4); + + public Span NvFences => MemoryMarshal.Cast(_storage); + + public static AndroidFence NoFence + { + get + { + AndroidFence fence = new AndroidFence + { + FenceCount = 0 + }; + + fence.NvFences[0].Id = NvFence.InvalidSyncPointId; + + return fence; + } + } + + public void AddFence(NvFence fence) + { + NvFences[FenceCount++] = fence; + } + + public void WaitForever(GpuContext gpuContext) + { + bool hasTimeout = Wait(gpuContext, TimeSpan.FromMilliseconds(3000)); + + if (hasTimeout) + { + Logger.PrintError(LogClass.SurfaceFlinger, "Android fence didn't signal in 3000 ms"); + Wait(gpuContext, Timeout.InfiniteTimeSpan); + } + + } + + public bool Wait(GpuContext gpuContext, TimeSpan timeout) + { + for (int i = 0; i < FenceCount; i++) + { + bool hasTimeout = NvFences[i].Wait(gpuContext, timeout); + + if (hasTimeout) + { + return true; + } + } + + return false; + } + + public uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf(); + } + + public uint GetFdCount() + { + return 0; + } + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref this); + } + + public void Unflatten(Parcel parcel) + { + this = parcel.ReadUnmanagedType(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs new file mode 100644 index 0000000000..c356671bc9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types +{ + class AndroidStrongPointer 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 other) + { + Object = other.Object; + _hasObject = other._hasObject; + } + + public void Set(T obj) + { + Object = obj; + _hasObject = true; + } + + public void Reset() + { + _hasObject = false; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs deleted file mode 100644 index 7b61a190f4..0000000000 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger -{ - struct BufferEntry - { - public BufferState State; - - public HalTransform Transform; - - public Rect Crop; - - public MultiFence Fence; - - public GbpBuffer Data; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs index 673da86013..1787f5a62a 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs @@ -1,10 +1,10 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { - enum BufferState + internal enum BufferState { - Free, - Dequeued, - Queued, - Acquired + Free = 0, + Dequeued = 1, + Queued = 2, + Acquired = 3 } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs deleted file mode 100644 index b93947fdec..0000000000 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Ryujinx.Common; -using System; -using System.IO; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger -{ - struct GbpBuffer - { - public GraphicBufferHeader Header { get; private set; } - public NvGraphicBuffer Buffer { get; private set; } - - public int Size => Marshal.SizeOf() + Marshal.SizeOf(); - - public GbpBuffer(BinaryReader reader) - { - Header = reader.ReadStruct(); - - // ignore fds - // TODO: check if that is used in official implementation - reader.BaseStream.Position += Header.FdsCount * 4; - - if (Header.IntsCount != 0x51) - { - throw new NotImplementedException($"Unexpected Graphic Buffer ints count (expected 0x51, found 0x{Header.IntsCount:x}"); - } - - Buffer = reader.ReadStruct(); - } - - public void Write(BinaryWriter writer) - { - writer.WriteStruct(Header); - writer.WriteStruct(Buffer); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs new file mode 100644 index 0000000000..8d63d9ccce --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs @@ -0,0 +1,75 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct GraphicBuffer : IFlattenable + { + public GraphicBufferHeader Header; + public NvGraphicBuffer Buffer; + + public int Width => Header.Width; + public int Height => Header.Height; + public PixelFormat Format => Header.Format; + public int Usage => Header.Usage; + + public Rect ToRect() + { + return new Rect(Width, Height); + } + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref Header); + parcel.WriteUnmanagedType(ref Buffer); + } + + public void Unflatten(Parcel parcel) + { + Header = parcel.ReadUnmanagedType(); + + int expectedSize = Unsafe.SizeOf() / 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(); + } + + public void IncrementNvMapHandleRefCount(KProcess process) + { + NvMapDeviceFile.IncrementMapRefCount(process, Buffer.NvMapId); + + for (int i = 0; i < Buffer.Surfaces.Length; i++) + { + NvMapDeviceFile.IncrementMapRefCount(process, Buffer.Surfaces[i].NvMapHandle); + } + } + + public void DecrementNvMapHandleRefCount(KProcess process) + { + NvMapDeviceFile.DecrementMapRefCount(process, Buffer.NvMapId); + + for (int i = 0; i < Buffer.Surfaces.Length; i++) + { + NvMapDeviceFile.DecrementMapRefCount(process, Buffer.Surfaces[i].NvMapHandle); + } + } + + public uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf(); + } + + public uint GetFdCount() + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs index fae0002f5b..77495922c8 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs @@ -2,15 +2,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { - [StructLayout(LayoutKind.Sequential, Size = 0x28)] + [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 1)] struct GraphicBufferHeader { - public int Magic; - public int Width; - public int Height; - public int Stride; - public int Format; - public int Usage; + public int Magic; + public int Width; + public int Height; + public int Stride; + public PixelFormat Format; + public int Usage; public int Pid; public int RefCount; diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs deleted file mode 100644 index a1efed0bc3..0000000000 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger -{ - [Flags] - enum HalTransform - { - FlipX = 1, - FlipY = 2, - Rotate90 = 4, - Rotate180 = FlipX | FlipY, - Rotate270 = Rotate90 | Rotate180 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs deleted file mode 100644 index 8ee62796f1..0000000000 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Ryujinx.Graphics.Gpu; -using Ryujinx.HLE.HOS.Services.Nv.Types; -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger -{ - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)] - struct MultiFence - { - public int FenceCount; - - private byte _fenceStorageStart; - - private Span _storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf() * 4); - - private Span _nvFences => MemoryMarshal.Cast(_storage); - - public static MultiFence NoFence - { - get - { - MultiFence fence = new MultiFence - { - FenceCount = 0 - }; - - fence._nvFences[0].Id = NvFence.InvalidSyncPointId; - - return fence; - } - } - - public void WaitForever(GpuContext gpuContext) - { - Wait(gpuContext, Timeout.InfiniteTimeSpan); - } - - public void Wait(GpuContext gpuContext, TimeSpan timeout) - { - for (int i = 0; i < FenceCount; i++) - { - _nvFences[i].Wait(gpuContext, timeout); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs index 9a52245cf4..6bb47dccff 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs @@ -2,7 +2,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { - [StructLayout(LayoutKind.Explicit, Size = 0x144)] + [StructLayout(LayoutKind.Explicit, Size = 0x144, Pack = 1)] struct NvGraphicBuffer { [FieldOffset(0x4)] diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs index 7327b5598c..51ac98f821 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs @@ -35,5 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger throw new IndexOutOfRangeException(); } } + + public int Length => 3; } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs deleted file mode 100644 index def3caef61..0000000000 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger -{ - [StructLayout(LayoutKind.Explicit, Pack = 1)] - struct QueueBufferObject - { - [FieldOffset(0x0)] - public long Timestamp; - - [FieldOffset(0x8)] - public int IsAutoTimestamp; - - [FieldOffset(0xC)] - public Rect Crop; - - [FieldOffset(0x1C)] - public int ScalingMode; - - [FieldOffset(0x20)] - public HalTransform Transform; - - [FieldOffset(0x24)] - public int StickyTransform; - - [FieldOffset(0x28)] - public int Unknown; - - [FieldOffset(0x2C)] - public int SwapInterval; - - [FieldOffset(0x30)] - public MultiFence Fence; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs index c2f51eeaf8..a5dec96945 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs @@ -1,13 +1,71 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { [StructLayout(LayoutKind.Sequential, Size = 0x10)] - struct Rect + struct Rect : IEquatable { - public int Top; public int Left; + public int Top; public int Right; public int Bottom; + + public int Width => Right - Left; + public int Height => Bottom - Top; + + public Rect(int width, int height) + { + Left = 0; + Top = 0; + Right = width; + Bottom = height; + } + + public bool IsEmpty() + { + return Width <= 0 || Height <= 0; + } + + public bool Intersect(Rect other, out Rect result) + { + result = new Rect + { + Left = Math.Max(Left, other.Left), + Top = Math.Max(Top, other.Top), + Right = Math.Min(Right, other.Right), + Bottom = Math.Min(Bottom, other.Bottom) + }; + + return !result.IsEmpty(); + } + + public void MakeInvalid() + { + Right = -1; + Bottom = -1; + } + + public static bool operator ==(Rect x, Rect y) + { + return x.Equals(y); + } + + public static bool operator !=(Rect x, Rect y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Rect rect && Equals(rect); + } + + public bool Equals(Rect cmpObj) + { + return Left == cmpObj.Left && Top == cmpObj.Top && Right == cmpObj.Right && Bottom == cmpObj.Bottom; + } + + public override int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom); } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs deleted file mode 100644 index e30d159cac..0000000000 --- a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Ryujinx.Graphics.GAL; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Common; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.SurfaceFlinger; -using System; - -namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService -{ - class IHOSBinderDriver : IpcService, IDisposable - { - private KEvent _binderEvent; - - private NvFlinger _flinger; - - public IHOSBinderDriver(Horizon system, IRenderer renderer) - { - _binderEvent = new KEvent(system); - - _binderEvent.ReadableEvent.Signal(); - - _flinger = new NvFlinger(renderer, _binderEvent); - } - - [Command(0)] - // TransactParcel(s32, u32, u32, buffer) -> buffer - public ResultCode TransactParcel(ServiceCtx context) - { - int id = context.RequestData.ReadInt32(); - int code = context.RequestData.ReadInt32(); - - long dataPos = context.Request.SendBuff[0].Position; - long dataSize = context.Request.SendBuff[0].Size; - - byte[] data = context.Memory.ReadBytes(dataPos, dataSize); - - data = Parcel.GetParcelData(data); - - return (ResultCode)_flinger.ProcessParcelRequest(context, data, code); - } - - [Command(1)] - // AdjustRefcount(s32, s32, s32) - public ResultCode AdjustRefcount(ServiceCtx context) - { - int id = context.RequestData.ReadInt32(); - int addVal = context.RequestData.ReadInt32(); - int type = context.RequestData.ReadInt32(); - - return ResultCode.Success; - } - - [Command(2)] - // GetNativeHandle(s32, s32) -> handle - public ResultCode GetNativeHandle(ServiceCtx context) - { - int id = context.RequestData.ReadInt32(); - uint unk = context.RequestData.ReadUInt32(); - - if (context.Process.HandleTable.GenerateHandle(_binderEvent.ReadableEvent, out int handle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); - - return ResultCode.Success; - } - - [Command(3)] // 3.0.0+ - // TransactParcelAuto(s32, u32, u32, buffer) -> buffer - public ResultCode TransactParcelAuto(ServiceCtx context) - { - int id = context.RequestData.ReadInt32(); - int code = context.RequestData.ReadInt32(); - - (long dataPos, long dataSize) = context.Request.GetBufferType0x21(); - - byte[] data = context.Memory.ReadBytes(dataPos, dataSize); - - data = Parcel.GetParcelData(data); - - return (ResultCode)_flinger.ProcessParcelRequest(context, data, code); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _flinger.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs index 24e73244ff..f8ef494ad3 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService { @@ -15,9 +16,12 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService // CreateManagedLayer(u32, u64, nn::applet::AppletResourceUserId) -> u64 public ResultCode CreateManagedLayer(ServiceCtx context) { - Logger.PrintStub(LogClass.ServiceVi); + long layerFlags = context.RequestData.ReadInt64(); + long displayId = context.RequestData.ReadInt64(); - context.ResponseData.Write(0L); //LayerId + context.Device.System.SurfaceFlinger.CreateLayer(context.Process, out long layerId); + + context.ResponseData.Write(layerId); return ResultCode.Success; } @@ -26,7 +30,9 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService // DestroyManagedLayer(u64) public ResultCode DestroyManagedLayer(ServiceCtx context) { - Logger.PrintStub(LogClass.ServiceVi); + long layerId = context.RequestData.ReadInt64(); + + context.Device.System.SurfaceFlinger.CloseLayer(layerId); return ResultCode.Success; } @@ -35,8 +41,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService // CreateStrayLayer(u32, u64) -> (u64, u64, buffer) public ResultCode CreateStrayLayer(ServiceCtx context) { - Logger.PrintStub(LogClass.ServiceVi); - return _applicationDisplayService.CreateStrayLayer(context); } diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs index a2b57e7487..b7869519b7 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs @@ -1,13 +1,11 @@ using ARMeilleure.Memory; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; using System; -using System.IO; using System.Text; -using static Ryujinx.HLE.HOS.Services.SurfaceFlinger.Parcel; - namespace Ryujinx.HLE.HOS.Services.Vi.RootService { class IApplicationDisplayService : IpcService @@ -23,9 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService // GetRelayService() -> object public ResultCode GetRelayService(ServiceCtx context) { - MakeObject(context, new IHOSBinderDriver( - context.Device.System, - context.Device.Gpu.Renderer)); + MakeObject(context, new HOSBinderDriverServer()); return ResultCode.Success; } @@ -52,9 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService // GetIndirectDisplayTransactionService() -> object public ResultCode GetIndirectDisplayTransactionService(ServiceCtx context) { - MakeObject(context, new IHOSBinderDriver( - context.Device.System, - context.Device.Gpu.Renderer)); + MakeObject(context, new HOSBinderDriverServer()); return ResultCode.Success; } @@ -71,8 +65,8 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService context.Memory.WriteBytes(recBuffPtr, Encoding.ASCII.GetBytes("Default")); context.Memory.WriteInt64(recBuffPtr + 0x40, 0x1L); context.Memory.WriteInt64(recBuffPtr + 0x48, 0x1L); - context.Memory.WriteInt64(recBuffPtr + 0x50, 1920L); - context.Memory.WriteInt64(recBuffPtr + 0x58, 1080L); + context.Memory.WriteInt64(recBuffPtr + 0x50, 1280L); + context.Memory.WriteInt64(recBuffPtr + 0x58, 720L); context.ResponseData.Write(1L); @@ -119,16 +113,24 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService // OpenLayer(nn::vi::DisplayName, u64, nn::applet::AppletResourceUserId, pid) -> (u64, buffer) public ResultCode OpenLayer(ServiceCtx context) { - long layerId = context.RequestData.ReadInt64(); - long userId = context.RequestData.ReadInt64(); + // TODO: support multi display. + byte[] displayName = context.RequestData.ReadBytes(0x40); + long layerId = context.RequestData.ReadInt64(); + long userId = context.RequestData.ReadInt64(); long parcelPtr = context.Request.ReceiveBuff[0].Position; - byte[] parcel = MakeIGraphicsBufferProducer(parcelPtr); + IBinder producer = context.Device.System.SurfaceFlinger.OpenLayer(context.Process, layerId); - context.Memory.WriteBytes(parcelPtr, parcel); + Parcel parcel = new Parcel(0x28, 0x4); - context.ResponseData.Write((long)parcel.Length); + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan parcelData = parcel.Finish(); + + context.Memory.WriteBytes(parcelPtr, parcelData.ToArray()); + + context.ResponseData.Write((long)parcelData.Length); return ResultCode.Success; } @@ -139,6 +141,8 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService { long layerId = context.RequestData.ReadInt64(); + context.Device.System.SurfaceFlinger.CloseLayer(layerId); + return ResultCode.Success; } @@ -151,14 +155,21 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService long parcelPtr = context.Request.ReceiveBuff[0].Position; + // TODO: support multi display. Display disp = _displays.GetData((int)displayId); - byte[] parcel = MakeIGraphicsBufferProducer(parcelPtr); + IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(context.Process, out long layerId); - context.Memory.WriteBytes(parcelPtr, parcel); + Parcel parcel = new Parcel(0x28, 0x4); - context.ResponseData.Write(0L); - context.ResponseData.Write((long)parcel.Length); + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan parcelData = parcel.Finish(); + + context.Memory.WriteBytes(parcelPtr, parcelData.ToArray()); + + context.ResponseData.Write(layerId); + context.ResponseData.Write((long)parcelData.Length); return ResultCode.Success; } @@ -167,6 +178,10 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService // DestroyStrayLayer(u64) public ResultCode DestroyStrayLayer(ServiceCtx context) { + long layerId = context.RequestData.ReadInt64(); + + context.Device.System.SurfaceFlinger.CloseLayer(layerId); + return ResultCode.Success; } @@ -236,36 +251,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService return ResultCode.Success; } - private byte[] MakeIGraphicsBufferProducer(long basePtr) - { - long id = 0x20; - long cookiePtr = 0L; - - using (MemoryStream ms = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(ms); - - // flat_binder_object (size is 0x28) - writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER) - writer.Write(0); //Flags - writer.Write((int)(id >> 0)); - writer.Write((int)(id >> 32)); - writer.Write((int)(cookiePtr >> 0)); - writer.Write((int)(cookiePtr >> 32)); - writer.Write((byte)'d'); - writer.Write((byte)'i'); - writer.Write((byte)'s'); - writer.Write((byte)'p'); - writer.Write((byte)'d'); - writer.Write((byte)'r'); - writer.Write((byte)'v'); - writer.Write((byte)'\0'); - writer.Write(0L); //Pad - - return MakeParcel(ms.ToArray(), new byte[] { 0, 0, 0, 0 }); - } - } - private string GetDisplayName(ServiceCtx context) { string name = string.Empty; diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index c9b8daa534..ea3ab2385f 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -32,8 +32,6 @@ namespace Ryujinx.HLE public bool EnableDeviceVsync { get; set; } = true; - public AutoResetEvent VsyncEvent { get; private set; } - public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, IRenderer renderer, IAalOutput audioOut) { if (renderer == null) @@ -60,8 +58,6 @@ namespace Ryujinx.HLE Hid = new Hid(this, System.HidBaseAddress); Hid.InitDevices(); - - VsyncEvent = new AutoResetEvent(true); } public void Initialize() @@ -156,7 +152,6 @@ namespace Ryujinx.HLE if (disposing) { System.Dispose(); - VsyncEvent.Dispose(); AudioOut.Dispose(); } } diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs index 335b06a84c..d266eefcde 100644 --- a/Ryujinx/Ui/GLRenderer.cs +++ b/Ryujinx/Ui/GLRenderer.cs @@ -349,10 +349,6 @@ namespace Ryujinx.Ui $"Game: {_device.Statistics.GetGameFrameRate():00.00} FPS", $"GPU: {_renderer.GpuVendor}")); - _device.System.SignalVsync(); - - _device.VsyncEvent.Set(); - _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); } }