mirror of
https://github.com/ryujinx-mirror/ryujinx.git
synced 2024-11-24 11:05:12 +00:00
36749c358d
* Rewrite SurfaceFlinger Reimplement accurately SurfaceFlinger (based on my 8.1.0 reversing of it) TODO: support swap interval properly and reintroduce disabled "game vsync" support. * Some fixes for SetBufferCount * uncomment a test from last commit * SurfaceFlinger: don't free the graphic buffer in SetBufferCount * SurfaceFlinger: Implement swap interval correctly * SurfaceFlinger: Reintegrate Game VSync toggle * SurfaceFlinger: do not push a fence on buffer release on the consumer side * Revert "SurfaceFlinger: do not push a fence on buffer release on the consumer side" This reverts commit 586b52b0bfab2d11f361f4b59ab7b7141020bbad. * Make the game vsync toggle work dynamically again * Unregister producer's Binder object when closing layer * Address ripinperi's comments * Add a timeout on syncpoint wait operation Syncpoint aren't supposed to be waited on for more than a second. This effectively workaround issues caused by not having a channel scheduling in place yet. PS: Also introduce Android WaitForever warning about fence being not signaled for 3s * Fix a print of previous commit * Address Ac_K's comments * Address gdkchan's comments * Address final comments
283 lines
8.5 KiB
C#
283 lines
8.5 KiB
C#
using Ryujinx.Common.Logging;
|
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|
{
|
|
class BufferQueueCore
|
|
{
|
|
public BufferSlotArray Slots;
|
|
public int OverrideMaxBufferCount;
|
|
public bool UseAsyncBuffer;
|
|
public bool DequeueBufferCannotBlock;
|
|
public PixelFormat DefaultBufferFormat;
|
|
public int DefaultWidth;
|
|
public int DefaultHeight;
|
|
public int DefaultMaxBufferCount;
|
|
public int MaxAcquiredBufferCount;
|
|
public bool BufferHasBeenQueued;
|
|
public ulong FrameCounter;
|
|
public NativeWindowTransform TransformHint;
|
|
public bool IsAbandoned;
|
|
public NativeWindowApi ConnectedApi;
|
|
public bool IsAllocating;
|
|
public IProducerListener ProducerListener;
|
|
public IConsumerListener ConsumerListener;
|
|
public bool ConsumerControlledByApp;
|
|
public uint ConsumerUsageBits;
|
|
public List<BufferItem> Queue;
|
|
|
|
public readonly object Lock = new object();
|
|
|
|
private KEvent _waitBufferFreeEvent;
|
|
private KEvent _frameAvailableEvent;
|
|
|
|
public KProcess Owner { get; }
|
|
|
|
public BufferQueueCore(Switch device, KProcess process)
|
|
{
|
|
Slots = new BufferSlotArray();
|
|
IsAbandoned = false;
|
|
OverrideMaxBufferCount = 0;
|
|
DequeueBufferCannotBlock = false;
|
|
UseAsyncBuffer = false;
|
|
DefaultWidth = 1;
|
|
DefaultHeight = 1;
|
|
DefaultMaxBufferCount = 2;
|
|
MaxAcquiredBufferCount = 1;
|
|
FrameCounter = 0;
|
|
TransformHint = 0;
|
|
DefaultBufferFormat = PixelFormat.Rgba8888;
|
|
IsAllocating = false;
|
|
ProducerListener = null;
|
|
ConsumerListener = null;
|
|
ConsumerUsageBits = 0;
|
|
|
|
Queue = new List<BufferItem>();
|
|
|
|
// TODO: CreateGraphicBufferAlloc?
|
|
|
|
_waitBufferFreeEvent = new KEvent(device.System);
|
|
_frameAvailableEvent = new KEvent(device.System);
|
|
|
|
Owner = process;
|
|
}
|
|
|
|
public int GetMinUndequeuedBufferCountLocked(bool async)
|
|
{
|
|
if (!UseAsyncBuffer)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (DequeueBufferCannotBlock || async)
|
|
{
|
|
return MaxAcquiredBufferCount + 1;
|
|
}
|
|
|
|
return MaxAcquiredBufferCount;
|
|
}
|
|
|
|
public int GetMinMaxBufferCountLocked(bool async)
|
|
{
|
|
return GetMinUndequeuedBufferCountLocked(async);
|
|
}
|
|
|
|
public int GetMaxBufferCountLocked(bool async)
|
|
{
|
|
int minMaxBufferCount = GetMinMaxBufferCountLocked(async);
|
|
|
|
int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount);
|
|
|
|
if (OverrideMaxBufferCount != 0)
|
|
{
|
|
maxBufferCount = OverrideMaxBufferCount;
|
|
}
|
|
|
|
// Preserve all buffers already in control of the producer and the consumer.
|
|
for (int slot = maxBufferCount; slot < Slots.Length; slot++)
|
|
{
|
|
BufferState state = Slots[slot].BufferState;
|
|
|
|
if (state == BufferState.Queued || state == BufferState.Dequeued)
|
|
{
|
|
maxBufferCount = slot + 1;
|
|
}
|
|
}
|
|
|
|
return maxBufferCount;
|
|
}
|
|
|
|
public Status SetDefaultMaxBufferCountLocked(int count)
|
|
{
|
|
int minBufferCount = UseAsyncBuffer ? 2 : 1;
|
|
|
|
if (count < minBufferCount || count > Slots.Length)
|
|
{
|
|
return Status.BadValue;
|
|
}
|
|
|
|
DefaultMaxBufferCount = count;
|
|
|
|
SignalDequeueEvent();
|
|
|
|
return Status.Success;
|
|
}
|
|
|
|
public void SignalWaitBufferFreeEvent()
|
|
{
|
|
_waitBufferFreeEvent.WritableEvent.Signal();
|
|
}
|
|
|
|
public void SignalFrameAvailableEvent()
|
|
{
|
|
_frameAvailableEvent.WritableEvent.Signal();
|
|
}
|
|
|
|
// TODO: Find an accurate way to handle a regular condvar here as this will wake up unwanted threads in some edge cases.
|
|
public void SignalDequeueEvent()
|
|
{
|
|
Monitor.PulseAll(Lock);
|
|
}
|
|
|
|
public void WaitDequeueEvent()
|
|
{
|
|
Monitor.Wait(Lock);
|
|
}
|
|
|
|
public void SignalIsAbandonedEvent()
|
|
{
|
|
Monitor.PulseAll(Lock);
|
|
}
|
|
|
|
public void WaitIsAbandonedEvent()
|
|
{
|
|
Monitor.Wait(Lock);
|
|
}
|
|
|
|
public void FreeBufferLocked(int slot)
|
|
{
|
|
Slots[slot].GraphicBuffer.Reset();
|
|
|
|
if (Slots[slot].BufferState == BufferState.Acquired)
|
|
{
|
|
Slots[slot].NeedsCleanupOnRelease = true;
|
|
}
|
|
|
|
Slots[slot].BufferState = BufferState.Free;
|
|
Slots[slot].FrameNumber = uint.MaxValue;
|
|
Slots[slot].AcquireCalled = false;
|
|
Slots[slot].Fence.FenceCount = 0;
|
|
}
|
|
|
|
public void FreeAllBuffersLocked()
|
|
{
|
|
BufferHasBeenQueued = false;
|
|
|
|
for (int slot = 0; slot < Slots.Length; slot++)
|
|
{
|
|
FreeBufferLocked(slot);
|
|
}
|
|
}
|
|
|
|
public bool StillTracking(ref BufferItem item)
|
|
{
|
|
BufferSlot slot = Slots[item.Slot];
|
|
|
|
// TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be.
|
|
return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
|
|
}
|
|
|
|
public void WaitWhileAllocatingLocked()
|
|
{
|
|
while (IsAbandoned)
|
|
{
|
|
WaitIsAbandonedEvent();
|
|
}
|
|
}
|
|
|
|
public void CheckSystemEventsLocked(int maxBufferCount)
|
|
{
|
|
bool needBufferReleaseSignal = false;
|
|
bool needFrameAvailableSignal = false;
|
|
|
|
if (maxBufferCount > 1)
|
|
{
|
|
for (int i = 0; i < maxBufferCount; i++)
|
|
{
|
|
if (Slots[i].BufferState == BufferState.Queued)
|
|
{
|
|
needFrameAvailableSignal = true;
|
|
}
|
|
else if (Slots[i].BufferState == BufferState.Free)
|
|
{
|
|
needBufferReleaseSignal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needBufferReleaseSignal)
|
|
{
|
|
SignalWaitBufferFreeEvent();
|
|
}
|
|
else
|
|
{
|
|
_waitBufferFreeEvent.WritableEvent.Clear();
|
|
}
|
|
|
|
if (needFrameAvailableSignal)
|
|
{
|
|
SignalFrameAvailableEvent();
|
|
}
|
|
else
|
|
{
|
|
_frameAvailableEvent.WritableEvent.Clear();
|
|
}
|
|
}
|
|
|
|
public bool IsProducerConnectedLocked()
|
|
{
|
|
return ConnectedApi != NativeWindowApi.NoApi;
|
|
}
|
|
|
|
public bool IsConsumerConnectedLocked()
|
|
{
|
|
return ConsumerListener != null;
|
|
}
|
|
|
|
public KReadableEvent GetWaitBufferFreeEvent()
|
|
{
|
|
lock (Lock)
|
|
{
|
|
return _waitBufferFreeEvent.ReadableEvent;
|
|
}
|
|
}
|
|
|
|
public bool IsOwnedByConsumerLocked(int slot)
|
|
{
|
|
if (Slots[slot].BufferState != BufferState.Acquired)
|
|
{
|
|
Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the consumer (state = {Slots[slot].BufferState})");
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool IsOwnedByProducerLocked(int slot)
|
|
{
|
|
if (Slots[slot].BufferState != BufferState.Dequeued)
|
|
{
|
|
Logger.PrintError(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})");
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|