diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs index fcbbf5f34..0465cfccb 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs @@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { int numAcquiredBuffers = 0; - for (int i = 0; i < Core.Slots.Length; i++) + for (int i = 0; i < Core.MaxBufferCountCached; i++) { if (Core.Slots[i].BufferState == BufferState.Acquired) { @@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger } } - if (numAcquiredBuffers >= Core.MaxAcquiredBufferCount + 1) + if (numAcquiredBuffers > Core.MaxAcquiredBufferCount) { bufferItem = null; @@ -153,6 +153,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return Status.NoMemory; } + Core.UpdateMaxBufferCountCachedLocked(freeSlot); + slot = freeSlot; Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs index 7386bafc9..0b529a0ee 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs @@ -33,6 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger public BufferInfo[] BufferHistory; public uint BufferHistoryPosition; public bool EnableExternalEvent; + public int MaxBufferCountCached; public readonly object Lock = new object(); @@ -71,8 +72,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger Owner = process; - BufferHistory = new BufferInfo[BufferHistoryArraySize]; - EnableExternalEvent = true; + BufferHistory = new BufferInfo[BufferHistoryArraySize]; + EnableExternalEvent = true; + MaxBufferCountCached = 0; } public int GetMinUndequeuedBufferCountLocked(bool async) @@ -95,6 +97,14 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return GetMinUndequeuedBufferCountLocked(async); } + public void UpdateMaxBufferCountCachedLocked(int slot) + { + if (MaxBufferCountCached <= slot) + { + MaxBufferCountCached = slot + 1; + } + } + public int GetMaxBufferCountLocked(bool async) { int minMaxBufferCount = GetMinMaxBufferCountLocked(async); @@ -103,7 +113,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger if (OverrideMaxBufferCount != 0) { - maxBufferCount = OverrideMaxBufferCount; + return OverrideMaxBufferCount; } // Preserve all buffers already in control of the producer and the consumer. @@ -163,18 +173,20 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger Monitor.Wait(Lock); } - public void SignalIsAbandonedEvent() + public void SignalIsAllocatingEvent() { Monitor.PulseAll(Lock); } - public void WaitIsAbandonedEvent() + public void WaitIsAllocatingEvent() { Monitor.Wait(Lock); } public void FreeBufferLocked(int slot) { + Slots[slot].GraphicBuffer.Reset(); + if (Slots[slot].BufferState == BufferState.Acquired) { Slots[slot].NeedsCleanupOnRelease = true; @@ -206,9 +218,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger public void WaitWhileAllocatingLocked() { - while (IsAbandoned) + while (IsAllocating) { - WaitIsAbandonedEvent(); + WaitIsAllocatingEvent(); } } diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs index fb5d21940..3f1eaafe5 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Settings; using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; using Ryujinx.HLE.HOS.Services.Time.Clock; using System; @@ -92,9 +93,22 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return Status.BadValue; } - Core.Queue.Clear(); - Core.FreeAllBuffersLocked(); + int preallocatedBufferCount = GetPreallocatedBufferCountLocked(); + + if (preallocatedBufferCount <= 0) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + } + else if (preallocatedBufferCount < bufferCount) + { + Logger.PrintError(LogClass.SurfaceFlinger, "Not enough buffers. Try with more pre-allocated buffers"); + + return Status.Success; + } + Core.OverrideMaxBufferCount = bufferCount; + Core.SignalDequeueEvent(); Core.SignalWaitBufferFreeEvent(); @@ -162,8 +176,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger height = (uint)Core.DefaultHeight; } - Core.Slots[slot].BufferState = BufferState.Dequeued; - GraphicBuffer graphicBuffer = Core.Slots[slot].GraphicBuffer.Object; if (Core.Slots[slot].GraphicBuffer.IsNull @@ -172,7 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger || graphicBuffer.Format != format || (graphicBuffer.Usage & usage) != usage) { - if (Core.Slots[slot].GraphicBuffer.IsNull) + if (!Core.Slots[slot].IsPreallocated) { slot = BufferSlotArray.InvalidBufferSlot; fence = AndroidFence.NoFence; @@ -194,9 +206,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger } } + Core.Slots[slot].BufferState = BufferState.Dequeued; + + Core.UpdateMaxBufferCountCachedLocked(slot); + fence = Core.Slots[slot].Fence; - Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].QueueTime = TimeSpanType.Zero; + Core.Slots[slot].PresentationTime = TimeSpanType.Zero; Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(async)); } @@ -285,6 +303,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { lock (Core.Lock) { + Core.WaitWhileAllocatingLocked(); + Status status = WaitForFreeSlotThenRelock(false, out slot, out Status returnFlags); if (status != Status.Success) @@ -299,6 +319,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return Status.Busy; } + Core.UpdateMaxBufferCountCachedLocked(slot); + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); Core.Slots[slot].BufferState = BufferState.Dequeued; @@ -441,6 +463,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger NumPendingBuffers = (uint)Core.Queue.Count }; + if ((input.StickyTransform & 8) != 0) + { + output.TransformHint |= NativeWindowTransform.ReturnFrameNumber; + output.FrameNumber = Core.Slots[slot].FrameNumber; + } + _callbackTicket = _nextCallbackTicket++; } @@ -475,6 +503,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger Core.Slots[slot].FrameNumber = 0; Core.Slots[slot].Fence = fence; Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); } } @@ -550,6 +579,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger output.Height = (uint)Core.DefaultHeight; output.TransformHint = Core.TransformHint; output.NumPendingBuffers = (uint)Core.Queue.Count; + + if (NxSettings.Settings.TryGetValue("nv!nvn_no_vsync_capability", out object noVSyncCapability) && (bool)noVSyncCapability) + { + output.TransformHint |= NativeWindowTransform.NoVSyncCapability; + } + return Status.Success; default: return Status.BadValue; @@ -565,6 +600,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger lock (Core.Lock) { + Core.WaitWhileAllocatingLocked(); + if (Core.IsAbandoned) { return Status.Success; @@ -580,13 +617,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { Core.Queue.Clear(); Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); producerListener = Core.ProducerListener; Core.ProducerListener = null; Core.ConnectedApi = NativeWindowApi.NoApi; - Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + Core.SignalFrameAvailableEvent(); status = Status.Success; } @@ -599,6 +638,21 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return status; } + private int GetPreallocatedBufferCountLocked() + { + int bufferCount = 0; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (Core.Slots[i].IsPreallocated) + { + bufferCount++; + } + } + + return bufferCount; + } + public override Status SetPreallocatedBuffer(int slot, AndroidStrongPointer graphicBuffer) { if (slot < 0 || slot >= Core.Slots.Length) @@ -613,6 +667,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger Core.Slots[slot].RequestBufferCalled = false; Core.Slots[slot].AcquireCalled = false; Core.Slots[slot].NeedsCleanupOnRelease = false; + Core.Slots[slot].IsPreallocated = !graphicBuffer.IsNull; Core.Slots[slot].FrameNumber = 0; Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); @@ -622,21 +677,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger Core.Slots[slot].GraphicBuffer.Object.Buffer.Usage &= (int)Core.ConsumerUsageBits; } - int bufferCount = 0; - - for (int i = 0; i < Core.Slots.Length; i++) - { - if (!Core.Slots[i].GraphicBuffer.IsNull) - { - bufferCount++; - } - } - - Core.OverrideMaxBufferCount = bufferCount; + Core.OverrideMaxBufferCount = GetPreallocatedBufferCountLocked(); Core.UseAsyncBuffer = false; - bool cleared = false; - if (!graphicBuffer.IsNull) { // NOTE: Nintendo set the default width, height and format from the GraphicBuffer.. @@ -647,30 +690,30 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger } else { - foreach (BufferItem item in Core.Queue) + bool allBufferFreed = true; + + for (int i = 0; i < Core.Slots.Length; i++) { - if (item.Slot >= BufferSlotArray.NumBufferSlots) + if (!Core.Slots[i].GraphicBuffer.IsNull) { - Core.Queue.Clear(); - Core.FreeAllBuffersLocked(); - Core.SignalDequeueEvent(); - Core.SignalWaitBufferFreeEvent(); - Core.SignalFrameAvailableEvent(); - - cleared = true; - + allBufferFreed = false; break; } } + + if (allBufferFreed) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + Core.SignalFrameAvailableEvent(); + + return Status.Success; + } } - // 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.SignalDequeueEvent(); Core.SignalWaitBufferFreeEvent(); return Status.Success; @@ -702,12 +745,16 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return Status.BadValue; } - for (int slot = maxBufferCount; slot < Core.Slots.Length; slot++) + + if (maxBufferCount < Core.MaxBufferCountCached) { - if (!Core.Slots[slot].GraphicBuffer.IsNull) + for (int slot = maxBufferCount; slot < Core.MaxBufferCountCached; slot++) { - Core.FreeBufferLocked(slot); - returnStatus |= Status.ReleaseAllBuffers; + if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull && !Core.Slots[slot].IsPreallocated) + { + Core.FreeBufferLocked(slot); + returnStatus |= Status.ReleaseAllBuffers; + } } } diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs index 2f17f8a2c..fb84934a2 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs @@ -15,6 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger public bool AttachedByConsumer; public TimeSpanType QueueTime; public TimeSpanType PresentationTime; + public bool IsPreallocated; public BufferSlot() { @@ -22,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger BufferState = BufferState.Free; QueueTime = TimeSpanType.Zero; PresentationTime = TimeSpanType.Zero; + IsPreallocated = false; } } } diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs index 3b4996c8a..450d21a2a 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs @@ -72,6 +72,20 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger public uint Height; public NativeWindowTransform TransformHint; public uint NumPendingBuffers; + public ulong FrameNumber; + + public void WriteToParcel(Parcel parcel) + { + parcel.WriteUInt32(Width); + parcel.WriteUInt32(Height); + parcel.WriteUnmanagedType(ref TransformHint); + parcel.WriteUInt32(NumPendingBuffers); + + if (TransformHint.HasFlag(NativeWindowTransform.ReturnFrameNumber)) + { + parcel.WriteUInt64(FrameNumber); + } + } } public ResultCode AdjustRefcount(int addVal, int type) @@ -174,7 +188,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger status = QueueBuffer(slot, ref queueInput, out queueOutput); - outputParcel.WriteUnmanagedType(ref queueOutput); + queueOutput.WriteToParcel(outputParcel); outputParcel.WriteStatus(status); @@ -214,7 +228,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger status = Connect(listener, api, producerControlledByApp, out queueOutput); - outputParcel.WriteUnmanagedType(ref queueOutput); + queueOutput.WriteToParcel(outputParcel); outputParcel.WriteStatus(status); diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs index 650fe3c67..66482b123 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs @@ -5,12 +5,14 @@ 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 + None = 0, + FlipX = 1, + FlipY = 2, + Rotate90 = 4, + Rotate180 = FlipX | FlipY, + Rotate270 = Rotate90 | Rotate180, + InverseDisplay = 8, + NoVSyncCapability = 0x10, + ReturnFrameNumber = 0x20 } }