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