mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-19 18:16:54 +02:00
Move solution and projects to src
This commit is contained in:
parent
cd124bda58
commit
cee7121058
3466 changed files with 55 additions and 55 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs
Normal file
15
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
static class BufferQueue
|
||||
{
|
||||
public static BufferQueueCore CreateBufferQueue(Switch device, ulong pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer)
|
||||
{
|
||||
BufferQueueCore core = new BufferQueueCore(device, pid);
|
||||
|
||||
producer = new BufferQueueProducer(core, device.System.TickSource);
|
||||
consumer = new BufferQueueConsumer(core);
|
||||
|
||||
return core;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,420 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class BufferQueueConsumer
|
||||
{
|
||||
public BufferQueueCore Core { get; }
|
||||
|
||||
public BufferQueueConsumer(BufferQueueCore core)
|
||||
{
|
||||
Core = core;
|
||||
}
|
||||
|
||||
public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
int numAcquiredBuffers = 0;
|
||||
|
||||
for (int i = 0; i < Core.MaxBufferCountCached; i++)
|
||||
{
|
||||
if (Core.Slots[i].BufferState == BufferState.Acquired)
|
||||
{
|
||||
numAcquiredBuffers++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numAcquiredBuffers > Core.MaxAcquiredBufferCount)
|
||||
{
|
||||
bufferItem = null;
|
||||
|
||||
Logger.Debug?.Print(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})");
|
||||
|
||||
return Status.InvalidOperation;
|
||||
}
|
||||
|
||||
if (Core.Queue.Count == 0)
|
||||
{
|
||||
bufferItem = null;
|
||||
|
||||
return Status.NoBufferAvailaible;
|
||||
}
|
||||
|
||||
if (expectedPresent != 0)
|
||||
{
|
||||
// TODO: support this for advanced presenting.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bufferItem = Core.Queue[0];
|
||||
|
||||
if (Core.StillTracking(ref bufferItem))
|
||||
{
|
||||
Core.Slots[bufferItem.Slot].AcquireCalled = true;
|
||||
Core.Slots[bufferItem.Slot].NeedsCleanupOnRelease = true;
|
||||
Core.Slots[bufferItem.Slot].BufferState = BufferState.Acquired;
|
||||
Core.Slots[bufferItem.Slot].Fence = AndroidFence.NoFence;
|
||||
|
||||
ulong targetFrameNumber = Core.Slots[bufferItem.Slot].FrameNumber;
|
||||
|
||||
for (int i = 0; i < Core.BufferHistory.Length; i++)
|
||||
{
|
||||
if (Core.BufferHistory[i].FrameNumber == targetFrameNumber)
|
||||
{
|
||||
Core.BufferHistory[i].State = BufferState.Acquired;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferItem.AcquireCalled)
|
||||
{
|
||||
bufferItem.GraphicBuffer.Reset();
|
||||
}
|
||||
|
||||
Core.Queue.RemoveAt(0);
|
||||
|
||||
Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true));
|
||||
Core.SignalDequeueEvent();
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status DetachBuffer(int slot)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByConsumerLocked(slot))
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
if (!Core.Slots[slot].RequestBufferCalled)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer");
|
||||
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
Core.FreeBufferLocked(slot);
|
||||
Core.SignalDequeueEvent();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public Status AttachBuffer(out int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
int numAcquiredBuffers = 0;
|
||||
|
||||
int freeSlot = BufferSlotArray.InvalidBufferSlot;
|
||||
|
||||
for (int i = 0; i < Core.Slots.Length; i++)
|
||||
{
|
||||
if (Core.Slots[i].BufferState == BufferState.Acquired)
|
||||
{
|
||||
numAcquiredBuffers++;
|
||||
}
|
||||
else if (Core.Slots[i].BufferState == BufferState.Free)
|
||||
{
|
||||
if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[i].FrameNumber < Core.Slots[freeSlot].FrameNumber)
|
||||
{
|
||||
freeSlot = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numAcquiredBuffers > Core.MaxAcquiredBufferCount + 1)
|
||||
{
|
||||
slot = BufferSlotArray.InvalidBufferSlot;
|
||||
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})");
|
||||
|
||||
return Status.InvalidOperation;
|
||||
}
|
||||
|
||||
if (freeSlot == BufferSlotArray.InvalidBufferSlot)
|
||||
{
|
||||
slot = BufferSlotArray.InvalidBufferSlot;
|
||||
|
||||
return Status.NoMemory;
|
||||
}
|
||||
|
||||
Core.UpdateMaxBufferCountCachedLocked(freeSlot);
|
||||
|
||||
slot = freeSlot;
|
||||
|
||||
Core.Slots[slot].GraphicBuffer.Set(graphicBuffer);
|
||||
|
||||
Core.Slots[slot].BufferState = BufferState.Acquired;
|
||||
Core.Slots[slot].AttachedByConsumer = true;
|
||||
Core.Slots[slot].NeedsCleanupOnRelease = false;
|
||||
Core.Slots[slot].Fence = AndroidFence.NoFence;
|
||||
Core.Slots[slot].FrameNumber = 0;
|
||||
Core.Slots[slot].AcquireCalled = false;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status ReleaseBuffer(int slot, ulong frameNumber, ref AndroidFence fence)
|
||||
{
|
||||
if (slot < 0 || slot >= Core.Slots.Length)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
IProducerListener listener = null;
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.Slots[slot].FrameNumber != frameNumber)
|
||||
{
|
||||
return Status.StaleBufferSlot;
|
||||
}
|
||||
|
||||
foreach (BufferItem item in Core.Queue)
|
||||
{
|
||||
if (item.Slot == slot)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Core.Slots[slot].BufferState == BufferState.Acquired)
|
||||
{
|
||||
Core.Slots[slot].BufferState = BufferState.Free;
|
||||
Core.Slots[slot].Fence = fence;
|
||||
|
||||
listener = Core.ProducerListener;
|
||||
}
|
||||
else if (Core.Slots[slot].NeedsCleanupOnRelease)
|
||||
{
|
||||
Core.Slots[slot].NeedsCleanupOnRelease = false;
|
||||
|
||||
return Status.StaleBufferSlot;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner);
|
||||
|
||||
Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true));
|
||||
Core.SignalDequeueEvent();
|
||||
}
|
||||
|
||||
listener?.OnBufferReleased();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status Connect(IConsumerListener consumerListener, bool controlledByApp)
|
||||
{
|
||||
if (consumerListener == null)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
Core.ConsumerListener = consumerListener;
|
||||
Core.ConsumerControlledByApp = controlledByApp;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status Disconnect()
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (!Core.IsConsumerConnectedLocked())
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
Core.IsAbandoned = true;
|
||||
Core.ConsumerListener = null;
|
||||
|
||||
Core.Queue.Clear();
|
||||
Core.FreeAllBuffersLocked();
|
||||
Core.SignalDequeueEvent();
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status GetReleasedBuffers(out ulong slotMask)
|
||||
{
|
||||
slotMask = 0;
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
for (int slot = 0; slot < Core.Slots.Length; slot++)
|
||||
{
|
||||
if (!Core.Slots[slot].AcquireCalled)
|
||||
{
|
||||
slotMask |= 1UL << slot;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Core.Queue.Count; i++)
|
||||
{
|
||||
if (Core.Queue[i].AcquireCalled)
|
||||
{
|
||||
slotMask &= ~(1UL << i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status SetDefaultBufferSize(uint width, uint height)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.DefaultWidth = (int)width;
|
||||
Core.DefaultHeight = (int)height;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status SetDefaultMaxBufferCount(int bufferMaxCount)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
return Core.SetDefaultMaxBufferCountLocked(bufferMaxCount);
|
||||
}
|
||||
}
|
||||
|
||||
public Status DisableAsyncBuffer()
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsConsumerConnectedLocked())
|
||||
{
|
||||
return Status.InvalidOperation;
|
||||
}
|
||||
|
||||
Core.UseAsyncBuffer = false;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status SetMaxAcquiredBufferCount(int maxAcquiredBufferCount)
|
||||
{
|
||||
if (maxAcquiredBufferCount < 0 || maxAcquiredBufferCount > BufferSlotArray.MaxAcquiredBuffers)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsProducerConnectedLocked())
|
||||
{
|
||||
return Status.InvalidOperation;
|
||||
}
|
||||
|
||||
Core.MaxAcquiredBufferCount = maxAcquiredBufferCount;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status SetDefaultBufferFormat(PixelFormat defaultFormat)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.DefaultBufferFormat = defaultFormat;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status SetConsumerUsageBits(uint usage)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.ConsumerUsageBits = usage;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status SetTransformHint(NativeWindowTransform transformHint)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.TransformHint = transformHint;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public Status SetPresentTime(int slot, ulong frameNumber, TimeSpanType presentationTime)
|
||||
{
|
||||
if (slot < 0 || slot >= Core.Slots.Length)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.Slots[slot].FrameNumber != frameNumber)
|
||||
{
|
||||
return Status.StaleBufferSlot;
|
||||
}
|
||||
|
||||
if (Core.Slots[slot].PresentationTime.NanoSeconds == 0)
|
||||
{
|
||||
Core.Slots[slot].PresentationTime = presentationTime;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Core.BufferHistory.Length; i++)
|
||||
{
|
||||
if (Core.BufferHistory[i].FrameNumber == frameNumber)
|
||||
{
|
||||
Core.BufferHistory[i].PresentationTime = presentationTime;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
}
|
341
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
Normal file
341
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
Normal file
|
@ -0,0 +1,341 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class BufferQueueCore
|
||||
{
|
||||
public BufferSlotArray Slots;
|
||||
public int OverrideMaxBufferCount;
|
||||
public bool UseAsyncBuffer;
|
||||
public bool DequeueBufferCannotBlock;
|
||||
public PixelFormat DefaultBufferFormat;
|
||||
public int DefaultWidth;
|
||||
public int DefaultHeight;
|
||||
public int DefaultMaxBufferCount;
|
||||
public int MaxAcquiredBufferCount;
|
||||
public bool BufferHasBeenQueued;
|
||||
public ulong FrameCounter;
|
||||
public NativeWindowTransform TransformHint;
|
||||
public bool IsAbandoned;
|
||||
public NativeWindowApi ConnectedApi;
|
||||
public bool IsAllocating;
|
||||
public IProducerListener ProducerListener;
|
||||
public IConsumerListener ConsumerListener;
|
||||
public bool ConsumerControlledByApp;
|
||||
public uint ConsumerUsageBits;
|
||||
public List<BufferItem> Queue;
|
||||
public BufferInfo[] BufferHistory;
|
||||
public uint BufferHistoryPosition;
|
||||
public bool EnableExternalEvent;
|
||||
public int MaxBufferCountCached;
|
||||
|
||||
public readonly object Lock = new object();
|
||||
|
||||
private KEvent _waitBufferFreeEvent;
|
||||
private KEvent _frameAvailableEvent;
|
||||
|
||||
public ulong Owner { get; }
|
||||
|
||||
public bool Active { get; private set; }
|
||||
|
||||
public const int BufferHistoryArraySize = 8;
|
||||
|
||||
public event Action BufferQueued;
|
||||
|
||||
public BufferQueueCore(Switch device, ulong pid)
|
||||
{
|
||||
Slots = new BufferSlotArray();
|
||||
IsAbandoned = false;
|
||||
OverrideMaxBufferCount = 0;
|
||||
DequeueBufferCannotBlock = false;
|
||||
UseAsyncBuffer = false;
|
||||
DefaultWidth = 1;
|
||||
DefaultHeight = 1;
|
||||
DefaultMaxBufferCount = 2;
|
||||
MaxAcquiredBufferCount = 1;
|
||||
FrameCounter = 0;
|
||||
TransformHint = 0;
|
||||
DefaultBufferFormat = PixelFormat.Rgba8888;
|
||||
IsAllocating = false;
|
||||
ProducerListener = null;
|
||||
ConsumerListener = null;
|
||||
ConsumerUsageBits = 0;
|
||||
|
||||
Queue = new List<BufferItem>();
|
||||
|
||||
// TODO: CreateGraphicBufferAlloc?
|
||||
|
||||
_waitBufferFreeEvent = new KEvent(device.System.KernelContext);
|
||||
_frameAvailableEvent = new KEvent(device.System.KernelContext);
|
||||
|
||||
Owner = pid;
|
||||
|
||||
Active = true;
|
||||
|
||||
BufferHistory = new BufferInfo[BufferHistoryArraySize];
|
||||
EnableExternalEvent = true;
|
||||
MaxBufferCountCached = 0;
|
||||
}
|
||||
|
||||
public int GetMinUndequeuedBufferCountLocked(bool async)
|
||||
{
|
||||
if (!UseAsyncBuffer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (DequeueBufferCannotBlock || async)
|
||||
{
|
||||
return MaxAcquiredBufferCount + 1;
|
||||
}
|
||||
|
||||
return MaxAcquiredBufferCount;
|
||||
}
|
||||
|
||||
public int GetMinMaxBufferCountLocked(bool async)
|
||||
{
|
||||
return GetMinUndequeuedBufferCountLocked(async);
|
||||
}
|
||||
|
||||
public void UpdateMaxBufferCountCachedLocked(int slot)
|
||||
{
|
||||
if (MaxBufferCountCached <= slot)
|
||||
{
|
||||
MaxBufferCountCached = slot + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetMaxBufferCountLocked(bool async)
|
||||
{
|
||||
int minMaxBufferCount = GetMinMaxBufferCountLocked(async);
|
||||
|
||||
int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount);
|
||||
|
||||
if (OverrideMaxBufferCount != 0)
|
||||
{
|
||||
return OverrideMaxBufferCount;
|
||||
}
|
||||
|
||||
// Preserve all buffers already in control of the producer and the consumer.
|
||||
for (int slot = maxBufferCount; slot < Slots.Length; slot++)
|
||||
{
|
||||
BufferState state = Slots[slot].BufferState;
|
||||
|
||||
if (state == BufferState.Queued || state == BufferState.Dequeued)
|
||||
{
|
||||
maxBufferCount = slot + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return maxBufferCount;
|
||||
}
|
||||
|
||||
public Status SetDefaultMaxBufferCountLocked(int count)
|
||||
{
|
||||
int minBufferCount = UseAsyncBuffer ? 2 : 1;
|
||||
|
||||
if (count < minBufferCount || count > Slots.Length)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
DefaultMaxBufferCount = count;
|
||||
|
||||
SignalDequeueEvent();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public void SignalWaitBufferFreeEvent()
|
||||
{
|
||||
if (EnableExternalEvent)
|
||||
{
|
||||
_waitBufferFreeEvent.WritableEvent.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
public void SignalFrameAvailableEvent()
|
||||
{
|
||||
if (EnableExternalEvent)
|
||||
{
|
||||
_frameAvailableEvent.WritableEvent.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareForExit()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
Active = false;
|
||||
|
||||
Monitor.PulseAll(Lock);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Find an accurate way to handle a regular condvar here as this will wake up unwanted threads in some edge cases.
|
||||
public void SignalDequeueEvent()
|
||||
{
|
||||
Monitor.PulseAll(Lock);
|
||||
}
|
||||
|
||||
public void WaitDequeueEvent()
|
||||
{
|
||||
WaitForLock();
|
||||
}
|
||||
|
||||
public void SignalIsAllocatingEvent()
|
||||
{
|
||||
Monitor.PulseAll(Lock);
|
||||
}
|
||||
|
||||
public void WaitIsAllocatingEvent()
|
||||
{
|
||||
WaitForLock();
|
||||
}
|
||||
|
||||
public void SignalQueueEvent()
|
||||
{
|
||||
BufferQueued?.Invoke();
|
||||
}
|
||||
|
||||
private void WaitForLock()
|
||||
{
|
||||
if (Active)
|
||||
{
|
||||
Monitor.Wait(Lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void FreeBufferLocked(int slot)
|
||||
{
|
||||
Slots[slot].GraphicBuffer.Reset();
|
||||
|
||||
if (Slots[slot].BufferState == BufferState.Acquired)
|
||||
{
|
||||
Slots[slot].NeedsCleanupOnRelease = true;
|
||||
}
|
||||
|
||||
Slots[slot].BufferState = BufferState.Free;
|
||||
Slots[slot].FrameNumber = uint.MaxValue;
|
||||
Slots[slot].AcquireCalled = false;
|
||||
Slots[slot].Fence.FenceCount = 0;
|
||||
}
|
||||
|
||||
public void FreeAllBuffersLocked()
|
||||
{
|
||||
BufferHasBeenQueued = false;
|
||||
|
||||
for (int slot = 0; slot < Slots.Length; slot++)
|
||||
{
|
||||
FreeBufferLocked(slot);
|
||||
}
|
||||
}
|
||||
|
||||
public bool StillTracking(ref BufferItem item)
|
||||
{
|
||||
BufferSlot slot = Slots[item.Slot];
|
||||
|
||||
// TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be.
|
||||
return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
|
||||
}
|
||||
|
||||
public void WaitWhileAllocatingLocked()
|
||||
{
|
||||
while (IsAllocating)
|
||||
{
|
||||
WaitIsAllocatingEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckSystemEventsLocked(int maxBufferCount)
|
||||
{
|
||||
if (!EnableExternalEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool needBufferReleaseSignal = false;
|
||||
bool needFrameAvailableSignal = false;
|
||||
|
||||
if (maxBufferCount > 1)
|
||||
{
|
||||
for (int i = 0; i < maxBufferCount; i++)
|
||||
{
|
||||
if (Slots[i].BufferState == BufferState.Queued)
|
||||
{
|
||||
needFrameAvailableSignal = true;
|
||||
}
|
||||
else if (Slots[i].BufferState == BufferState.Free)
|
||||
{
|
||||
needBufferReleaseSignal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needBufferReleaseSignal)
|
||||
{
|
||||
SignalWaitBufferFreeEvent();
|
||||
}
|
||||
else
|
||||
{
|
||||
_waitBufferFreeEvent.WritableEvent.Clear();
|
||||
}
|
||||
|
||||
if (needFrameAvailableSignal)
|
||||
{
|
||||
SignalFrameAvailableEvent();
|
||||
}
|
||||
else
|
||||
{
|
||||
_frameAvailableEvent.WritableEvent.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProducerConnectedLocked()
|
||||
{
|
||||
return ConnectedApi != NativeWindowApi.NoApi;
|
||||
}
|
||||
|
||||
public bool IsConsumerConnectedLocked()
|
||||
{
|
||||
return ConsumerListener != null;
|
||||
}
|
||||
|
||||
public KReadableEvent GetWaitBufferFreeEvent()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
return _waitBufferFreeEvent.ReadableEvent;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOwnedByConsumerLocked(int slot)
|
||||
{
|
||||
if (Slots[slot].BufferState != BufferState.Acquired)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the consumer (state = {Slots[slot].BufferState})");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsOwnedByProducerLocked(int slot)
|
||||
{
|
||||
if (Slots[slot].BufferState != BufferState.Dequeued)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,871 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Settings;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class BufferQueueProducer : IGraphicBufferProducer
|
||||
{
|
||||
public BufferQueueCore Core { get; }
|
||||
|
||||
private readonly ITickSource _tickSource;
|
||||
|
||||
private uint _stickyTransform;
|
||||
|
||||
private uint _nextCallbackTicket;
|
||||
private uint _currentCallbackTicket;
|
||||
private uint _callbackTicket;
|
||||
|
||||
private readonly object _callbackLock = new object();
|
||||
|
||||
public BufferQueueProducer(BufferQueueCore core, ITickSource tickSource)
|
||||
{
|
||||
Core = core;
|
||||
_tickSource = tickSource;
|
||||
|
||||
_stickyTransform = 0;
|
||||
_callbackTicket = 0;
|
||||
_nextCallbackTicket = 0;
|
||||
_currentCallbackTicket = 0;
|
||||
}
|
||||
|
||||
public override Status RequestBuffer(int slot, out AndroidStrongPointer<GraphicBuffer> graphicBuffer)
|
||||
{
|
||||
graphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
graphicBuffer.Set(Core.Slots[slot].GraphicBuffer);
|
||||
|
||||
Core.Slots[slot].RequestBufferCalled = true;
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public override Status SetBufferCount(int bufferCount)
|
||||
{
|
||||
IConsumerListener listener = null;
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
if (bufferCount > BufferSlotArray.NumBufferSlots)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
for (int slot = 0; slot < Core.Slots.Length; slot++)
|
||||
{
|
||||
if (Core.Slots[slot].BufferState == BufferState.Dequeued)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferCount == 0)
|
||||
{
|
||||
Core.OverrideMaxBufferCount = 0;
|
||||
Core.SignalDequeueEvent();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
int minBufferSlots = Core.GetMinMaxBufferCountLocked(false);
|
||||
|
||||
if (bufferCount < minBufferSlots)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
int preallocatedBufferCount = GetPreallocatedBufferCountLocked();
|
||||
|
||||
if (preallocatedBufferCount <= 0)
|
||||
{
|
||||
Core.Queue.Clear();
|
||||
Core.FreeAllBuffersLocked();
|
||||
}
|
||||
else if (preallocatedBufferCount < bufferCount)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, "Not enough buffers. Try with more pre-allocated buffers");
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
Core.OverrideMaxBufferCount = bufferCount;
|
||||
|
||||
Core.SignalDequeueEvent();
|
||||
Core.SignalWaitBufferFreeEvent();
|
||||
|
||||
listener = Core.ConsumerListener;
|
||||
}
|
||||
|
||||
listener?.OnBuffersReleased();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public override Status DequeueBuffer(out int slot,
|
||||
out AndroidFence fence,
|
||||
bool async,
|
||||
uint width,
|
||||
uint height,
|
||||
PixelFormat format,
|
||||
uint usage)
|
||||
{
|
||||
if ((width == 0 && height != 0) || (height == 0 && width != 0))
|
||||
{
|
||||
slot = BufferSlotArray.InvalidBufferSlot;
|
||||
fence = AndroidFence.NoFence;
|
||||
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
Status returnFlags = Status.Success;
|
||||
|
||||
bool attachedByConsumer = false;
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (format == PixelFormat.Unknown)
|
||||
{
|
||||
format = Core.DefaultBufferFormat;
|
||||
}
|
||||
|
||||
usage |= Core.ConsumerUsageBits;
|
||||
|
||||
Status status = WaitForFreeSlotThenRelock(async, out slot, out returnFlags);
|
||||
|
||||
if (status != Status.Success)
|
||||
{
|
||||
slot = BufferSlotArray.InvalidBufferSlot;
|
||||
fence = AndroidFence.NoFence;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
if (slot == BufferSlotArray.InvalidBufferSlot)
|
||||
{
|
||||
fence = AndroidFence.NoFence;
|
||||
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, "No available buffer slots");
|
||||
|
||||
return Status.Busy;
|
||||
}
|
||||
|
||||
attachedByConsumer = Core.Slots[slot].AttachedByConsumer;
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
width = (uint)Core.DefaultWidth;
|
||||
height = (uint)Core.DefaultHeight;
|
||||
}
|
||||
|
||||
GraphicBuffer graphicBuffer = Core.Slots[slot].GraphicBuffer.Object;
|
||||
|
||||
if (Core.Slots[slot].GraphicBuffer.IsNull
|
||||
|| graphicBuffer.Width != width
|
||||
|| graphicBuffer.Height != height
|
||||
|| graphicBuffer.Format != format
|
||||
|| (graphicBuffer.Usage & usage) != usage)
|
||||
{
|
||||
if (!Core.Slots[slot].IsPreallocated)
|
||||
{
|
||||
slot = BufferSlotArray.InvalidBufferSlot;
|
||||
fence = AndroidFence.NoFence;
|
||||
|
||||
return Status.NoMemory;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger,
|
||||
$"Preallocated buffer mismatch - slot {slot}\n" +
|
||||
$"available: Width = {graphicBuffer.Width} Height = {graphicBuffer.Height} Format = {graphicBuffer.Format} Usage = {graphicBuffer.Usage:x} " +
|
||||
$"requested: Width = {width} Height = {height} Format = {format} Usage = {usage:x}");
|
||||
|
||||
slot = BufferSlotArray.InvalidBufferSlot;
|
||||
fence = AndroidFence.NoFence;
|
||||
|
||||
return Status.NoInit;
|
||||
}
|
||||
}
|
||||
|
||||
Core.Slots[slot].BufferState = BufferState.Dequeued;
|
||||
|
||||
Core.UpdateMaxBufferCountCachedLocked(slot);
|
||||
|
||||
fence = Core.Slots[slot].Fence;
|
||||
|
||||
Core.Slots[slot].Fence = AndroidFence.NoFence;
|
||||
Core.Slots[slot].QueueTime = TimeSpanType.Zero;
|
||||
Core.Slots[slot].PresentationTime = TimeSpanType.Zero;
|
||||
|
||||
Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(async));
|
||||
}
|
||||
|
||||
if (attachedByConsumer)
|
||||
{
|
||||
returnFlags |= Status.BufferNeedsReallocation;
|
||||
}
|
||||
|
||||
return returnFlags;
|
||||
}
|
||||
|
||||
public override Status DetachBuffer(int slot)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
if (!Core.Slots[slot].RequestBufferCalled)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer");
|
||||
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
Core.FreeBufferLocked(slot);
|
||||
Core.SignalDequeueEvent();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public override Status DetachNextBuffer(out AndroidStrongPointer<GraphicBuffer> graphicBuffer, out AndroidFence fence)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.WaitWhileAllocatingLocked();
|
||||
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
graphicBuffer = default;
|
||||
fence = AndroidFence.NoFence;
|
||||
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
int nextBufferSlot = BufferSlotArray.InvalidBufferSlot;
|
||||
|
||||
for (int slot = 0; slot < Core.Slots.Length; slot++)
|
||||
{
|
||||
if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull)
|
||||
{
|
||||
if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[nextBufferSlot].FrameNumber)
|
||||
{
|
||||
nextBufferSlot = slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot)
|
||||
{
|
||||
graphicBuffer = default;
|
||||
fence = AndroidFence.NoFence;
|
||||
|
||||
return Status.NoMemory;
|
||||
}
|
||||
|
||||
graphicBuffer = Core.Slots[nextBufferSlot].GraphicBuffer;
|
||||
fence = Core.Slots[nextBufferSlot].Fence;
|
||||
|
||||
Core.FreeBufferLocked(nextBufferSlot);
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public override Status AttachBuffer(out int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.WaitWhileAllocatingLocked();
|
||||
|
||||
Status status = WaitForFreeSlotThenRelock(false, out slot, out Status returnFlags);
|
||||
|
||||
if (status != Status.Success)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (slot == BufferSlotArray.InvalidBufferSlot)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, "No available buffer slots");
|
||||
|
||||
return Status.Busy;
|
||||
}
|
||||
|
||||
Core.UpdateMaxBufferCountCachedLocked(slot);
|
||||
|
||||
Core.Slots[slot].GraphicBuffer.Set(graphicBuffer);
|
||||
|
||||
Core.Slots[slot].BufferState = BufferState.Dequeued;
|
||||
Core.Slots[slot].Fence = AndroidFence.NoFence;
|
||||
Core.Slots[slot].RequestBufferCalled = true;
|
||||
|
||||
return returnFlags;
|
||||
}
|
||||
}
|
||||
|
||||
public override Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output)
|
||||
{
|
||||
output = default;
|
||||
|
||||
switch (input.ScalingMode)
|
||||
{
|
||||
case NativeWindowScalingMode.Freeze:
|
||||
case NativeWindowScalingMode.ScaleToWindow:
|
||||
case NativeWindowScalingMode.ScaleCrop:
|
||||
case NativeWindowScalingMode.Unknown:
|
||||
case NativeWindowScalingMode.NoScaleCrop:
|
||||
break;
|
||||
default:
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
BufferItem item = new BufferItem();
|
||||
|
||||
IConsumerListener frameAvailableListener = null;
|
||||
IConsumerListener frameReplaceListener = null;
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
int maxBufferCount = Core.GetMaxBufferCountLocked(input.Async != 0);
|
||||
|
||||
if (input.Async != 0 && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
if (!Core.Slots[slot].RequestBufferCalled)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was queued without requesting a buffer");
|
||||
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
input.Crop.Intersect(Core.Slots[slot].GraphicBuffer.Object.ToRect(), out Rect croppedRect);
|
||||
|
||||
if (croppedRect != input.Crop)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
Core.Slots[slot].Fence = input.Fence;
|
||||
Core.Slots[slot].BufferState = BufferState.Queued;
|
||||
Core.FrameCounter++;
|
||||
Core.Slots[slot].FrameNumber = Core.FrameCounter;
|
||||
Core.Slots[slot].QueueTime = TimeSpanType.FromTimeSpan(_tickSource.ElapsedTime);
|
||||
Core.Slots[slot].PresentationTime = TimeSpanType.Zero;
|
||||
|
||||
item.AcquireCalled = Core.Slots[slot].AcquireCalled;
|
||||
item.Crop = input.Crop;
|
||||
item.Transform = input.Transform;
|
||||
item.TransformToDisplayInverse = (input.Transform & NativeWindowTransform.InverseDisplay) == NativeWindowTransform.InverseDisplay;
|
||||
item.ScalingMode = input.ScalingMode;
|
||||
item.Timestamp = input.Timestamp;
|
||||
item.IsAutoTimestamp = input.IsAutoTimestamp != 0;
|
||||
item.SwapInterval = input.SwapInterval;
|
||||
item.FrameNumber = Core.FrameCounter;
|
||||
item.Slot = slot;
|
||||
item.Fence = input.Fence;
|
||||
item.IsDroppable = Core.DequeueBufferCannotBlock || input.Async != 0;
|
||||
|
||||
item.GraphicBuffer.Set(Core.Slots[slot].GraphicBuffer);
|
||||
item.GraphicBuffer.Object.IncrementNvMapHandleRefCount(Core.Owner);
|
||||
|
||||
Core.BufferHistoryPosition = (Core.BufferHistoryPosition + 1) % BufferQueueCore.BufferHistoryArraySize;
|
||||
|
||||
Core.BufferHistory[Core.BufferHistoryPosition] = new BufferInfo
|
||||
{
|
||||
FrameNumber = Core.FrameCounter,
|
||||
QueueTime = Core.Slots[slot].QueueTime,
|
||||
State = BufferState.Queued
|
||||
};
|
||||
|
||||
_stickyTransform = input.StickyTransform;
|
||||
|
||||
if (Core.Queue.Count == 0)
|
||||
{
|
||||
Core.Queue.Add(item);
|
||||
|
||||
frameAvailableListener = Core.ConsumerListener;
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferItem frontItem = Core.Queue[0];
|
||||
|
||||
if (frontItem.IsDroppable)
|
||||
{
|
||||
if (Core.StillTracking(ref frontItem))
|
||||
{
|
||||
Core.Slots[slot].BufferState = BufferState.Free;
|
||||
Core.Slots[slot].FrameNumber = 0;
|
||||
}
|
||||
|
||||
Core.Queue.RemoveAt(0);
|
||||
Core.Queue.Insert(0, item);
|
||||
|
||||
frameReplaceListener = Core.ConsumerListener;
|
||||
}
|
||||
else
|
||||
{
|
||||
Core.Queue.Add(item);
|
||||
|
||||
frameAvailableListener = Core.ConsumerListener;
|
||||
}
|
||||
}
|
||||
|
||||
Core.BufferHasBeenQueued = true;
|
||||
Core.SignalDequeueEvent();
|
||||
|
||||
Core.CheckSystemEventsLocked(maxBufferCount);
|
||||
|
||||
output = new QueueBufferOutput
|
||||
{
|
||||
Width = (uint)Core.DefaultWidth,
|
||||
Height = (uint)Core.DefaultHeight,
|
||||
TransformHint = Core.TransformHint,
|
||||
NumPendingBuffers = (uint)Core.Queue.Count
|
||||
};
|
||||
|
||||
if ((input.StickyTransform & 8) != 0)
|
||||
{
|
||||
output.TransformHint |= NativeWindowTransform.ReturnFrameNumber;
|
||||
output.FrameNumber = Core.Slots[slot].FrameNumber;
|
||||
}
|
||||
|
||||
_callbackTicket = _nextCallbackTicket++;
|
||||
}
|
||||
|
||||
lock (_callbackLock)
|
||||
{
|
||||
while (_callbackTicket != _currentCallbackTicket)
|
||||
{
|
||||
Monitor.Wait(_callbackLock);
|
||||
}
|
||||
|
||||
frameAvailableListener?.OnFrameAvailable(ref item);
|
||||
frameReplaceListener?.OnFrameReplaced(ref item);
|
||||
|
||||
_currentCallbackTicket++;
|
||||
|
||||
Monitor.PulseAll(_callbackLock);
|
||||
}
|
||||
|
||||
Core.SignalQueueEvent();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public override void CancelBuffer(int slot, ref AndroidFence fence)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned || slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Core.Slots[slot].BufferState = BufferState.Free;
|
||||
Core.Slots[slot].FrameNumber = 0;
|
||||
Core.Slots[slot].Fence = fence;
|
||||
Core.SignalDequeueEvent();
|
||||
Core.SignalWaitBufferFreeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public override Status Query(NativeWindowAttribute what, out int outValue)
|
||||
{
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
outValue = 0;
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
switch (what)
|
||||
{
|
||||
case NativeWindowAttribute.Width:
|
||||
outValue = Core.DefaultWidth;
|
||||
return Status.Success;
|
||||
case NativeWindowAttribute.Height:
|
||||
outValue = Core.DefaultHeight;
|
||||
return Status.Success;
|
||||
case NativeWindowAttribute.Format:
|
||||
outValue = (int)Core.DefaultBufferFormat;
|
||||
return Status.Success;
|
||||
case NativeWindowAttribute.MinUnqueuedBuffers:
|
||||
outValue = Core.GetMinUndequeuedBufferCountLocked(false);
|
||||
return Status.Success;
|
||||
case NativeWindowAttribute.ConsumerRunningBehind:
|
||||
outValue = Core.Queue.Count > 1 ? 1 : 0;
|
||||
return Status.Success;
|
||||
case NativeWindowAttribute.ConsumerUsageBits:
|
||||
outValue = (int)Core.ConsumerUsageBits;
|
||||
return Status.Success;
|
||||
case NativeWindowAttribute.MaxBufferCountAsync:
|
||||
outValue = Core.GetMaxBufferCountLocked(true);
|
||||
return Status.Success;
|
||||
default:
|
||||
outValue = 0;
|
||||
return Status.BadValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output)
|
||||
{
|
||||
output = new QueueBufferOutput();
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
if (Core.IsAbandoned || Core.ConsumerListener == null)
|
||||
{
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
if (Core.ConnectedApi != NativeWindowApi.NoApi)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
Core.BufferHasBeenQueued = false;
|
||||
Core.DequeueBufferCannotBlock = Core.ConsumerControlledByApp && producerControlledByApp;
|
||||
|
||||
switch (api)
|
||||
{
|
||||
case NativeWindowApi.NVN:
|
||||
case NativeWindowApi.CPU:
|
||||
case NativeWindowApi.Media:
|
||||
case NativeWindowApi.Camera:
|
||||
Core.ProducerListener = listener;
|
||||
Core.ConnectedApi = api;
|
||||
|
||||
output.Width = (uint)Core.DefaultWidth;
|
||||
output.Height = (uint)Core.DefaultHeight;
|
||||
output.TransformHint = Core.TransformHint;
|
||||
output.NumPendingBuffers = (uint)Core.Queue.Count;
|
||||
|
||||
if (NxSettings.Settings.TryGetValue("nv!nvn_no_vsync_capability", out object noVSyncCapability) && (bool)noVSyncCapability)
|
||||
{
|
||||
output.TransformHint |= NativeWindowTransform.NoVSyncCapability;
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
default:
|
||||
return Status.BadValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Status Disconnect(NativeWindowApi api)
|
||||
{
|
||||
IProducerListener producerListener = null;
|
||||
|
||||
Status status = Status.BadValue;
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.WaitWhileAllocatingLocked();
|
||||
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
switch (api)
|
||||
{
|
||||
case NativeWindowApi.NVN:
|
||||
case NativeWindowApi.CPU:
|
||||
case NativeWindowApi.Media:
|
||||
case NativeWindowApi.Camera:
|
||||
if (Core.ConnectedApi == api)
|
||||
{
|
||||
Core.Queue.Clear();
|
||||
Core.FreeAllBuffersLocked();
|
||||
Core.SignalDequeueEvent();
|
||||
|
||||
producerListener = Core.ProducerListener;
|
||||
|
||||
Core.ProducerListener = null;
|
||||
Core.ConnectedApi = NativeWindowApi.NoApi;
|
||||
|
||||
Core.SignalWaitBufferFreeEvent();
|
||||
Core.SignalFrameAvailableEvent();
|
||||
|
||||
status = Status.Success;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
producerListener?.OnBufferReleased();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private int GetPreallocatedBufferCountLocked()
|
||||
{
|
||||
int bufferCount = 0;
|
||||
|
||||
for (int i = 0; i < Core.Slots.Length; i++)
|
||||
{
|
||||
if (Core.Slots[i].IsPreallocated)
|
||||
{
|
||||
bufferCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return bufferCount;
|
||||
}
|
||||
|
||||
public override Status SetPreallocatedBuffer(int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer)
|
||||
{
|
||||
if (slot < 0 || slot >= Core.Slots.Length)
|
||||
{
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
Core.Slots[slot].BufferState = BufferState.Free;
|
||||
Core.Slots[slot].Fence = AndroidFence.NoFence;
|
||||
Core.Slots[slot].RequestBufferCalled = false;
|
||||
Core.Slots[slot].AcquireCalled = false;
|
||||
Core.Slots[slot].NeedsCleanupOnRelease = false;
|
||||
Core.Slots[slot].IsPreallocated = !graphicBuffer.IsNull;
|
||||
Core.Slots[slot].FrameNumber = 0;
|
||||
|
||||
Core.Slots[slot].GraphicBuffer.Set(graphicBuffer);
|
||||
|
||||
if (!Core.Slots[slot].GraphicBuffer.IsNull)
|
||||
{
|
||||
Core.Slots[slot].GraphicBuffer.Object.Buffer.Usage &= (int)Core.ConsumerUsageBits;
|
||||
}
|
||||
|
||||
Core.OverrideMaxBufferCount = GetPreallocatedBufferCountLocked();
|
||||
Core.UseAsyncBuffer = false;
|
||||
|
||||
if (!graphicBuffer.IsNull)
|
||||
{
|
||||
// NOTE: Nintendo set the default width, height and format from the GraphicBuffer..
|
||||
// This is entirely wrong and should only be controlled by the consumer...
|
||||
Core.DefaultWidth = graphicBuffer.Object.Width;
|
||||
Core.DefaultHeight = graphicBuffer.Object.Height;
|
||||
Core.DefaultBufferFormat = graphicBuffer.Object.Format;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool allBufferFreed = true;
|
||||
|
||||
for (int i = 0; i < Core.Slots.Length; i++)
|
||||
{
|
||||
if (!Core.Slots[i].GraphicBuffer.IsNull)
|
||||
{
|
||||
allBufferFreed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allBufferFreed)
|
||||
{
|
||||
Core.Queue.Clear();
|
||||
Core.FreeAllBuffersLocked();
|
||||
Core.SignalDequeueEvent();
|
||||
Core.SignalWaitBufferFreeEvent();
|
||||
Core.SignalFrameAvailableEvent();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
Core.SignalDequeueEvent();
|
||||
Core.SignalWaitBufferFreeEvent();
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
private Status WaitForFreeSlotThenRelock(bool async, out int freeSlot, out Status returnStatus)
|
||||
{
|
||||
bool tryAgain = true;
|
||||
|
||||
freeSlot = BufferSlotArray.InvalidBufferSlot;
|
||||
returnStatus = Status.Success;
|
||||
|
||||
while (tryAgain)
|
||||
{
|
||||
if (Core.IsAbandoned)
|
||||
{
|
||||
freeSlot = BufferSlotArray.InvalidBufferSlot;
|
||||
|
||||
return Status.NoInit;
|
||||
}
|
||||
|
||||
int maxBufferCount = Core.GetMaxBufferCountLocked(async);
|
||||
|
||||
if (async && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount)
|
||||
{
|
||||
freeSlot = BufferSlotArray.InvalidBufferSlot;
|
||||
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
|
||||
if (maxBufferCount < Core.MaxBufferCountCached)
|
||||
{
|
||||
for (int slot = maxBufferCount; slot < Core.MaxBufferCountCached; slot++)
|
||||
{
|
||||
if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull && !Core.Slots[slot].IsPreallocated)
|
||||
{
|
||||
Core.FreeBufferLocked(slot);
|
||||
returnStatus |= Status.ReleaseAllBuffers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeSlot = BufferSlotArray.InvalidBufferSlot;
|
||||
|
||||
int dequeuedCount = 0;
|
||||
int acquiredCount = 0;
|
||||
|
||||
for (int slot = 0; slot < maxBufferCount; slot++)
|
||||
{
|
||||
switch (Core.Slots[slot].BufferState)
|
||||
{
|
||||
case BufferState.Acquired:
|
||||
acquiredCount++;
|
||||
break;
|
||||
case BufferState.Dequeued:
|
||||
dequeuedCount++;
|
||||
break;
|
||||
case BufferState.Free:
|
||||
if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[freeSlot].FrameNumber)
|
||||
{
|
||||
freeSlot = slot;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The producer SHOULD call SetBufferCount otherwise it's not allowed to dequeue multiple buffers.
|
||||
if (Core.OverrideMaxBufferCount == 0 && dequeuedCount > 0)
|
||||
{
|
||||
return Status.InvalidOperation;
|
||||
}
|
||||
|
||||
if (Core.BufferHasBeenQueued)
|
||||
{
|
||||
int newUndequeuedCount = maxBufferCount - (dequeuedCount + 1);
|
||||
int minUndequeuedCount = Core.GetMinUndequeuedBufferCountLocked(async);
|
||||
|
||||
if (newUndequeuedCount < minUndequeuedCount)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Min undequeued buffer count ({minUndequeuedCount}) exceeded (dequeued = {dequeuedCount} undequeued = {newUndequeuedCount})");
|
||||
|
||||
return Status.InvalidOperation;
|
||||
}
|
||||
}
|
||||
|
||||
bool tooManyBuffers = Core.Queue.Count > maxBufferCount;
|
||||
|
||||
tryAgain = freeSlot == BufferSlotArray.InvalidBufferSlot || tooManyBuffers;
|
||||
|
||||
if (tryAgain)
|
||||
{
|
||||
if (async || (Core.DequeueBufferCannotBlock && acquiredCount < Core.MaxAcquiredBufferCount))
|
||||
{
|
||||
Core.CheckSystemEventsLocked(maxBufferCount);
|
||||
|
||||
return Status.WouldBlock;
|
||||
}
|
||||
|
||||
Core.WaitDequeueEvent();
|
||||
|
||||
if (!Core.Active)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
protected override KReadableEvent GetWaitBufferFreeEvent()
|
||||
{
|
||||
return Core.GetWaitBufferFreeEvent();
|
||||
}
|
||||
|
||||
public override Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos)
|
||||
{
|
||||
if (bufferHistoryCount <= 0)
|
||||
{
|
||||
bufferInfos = Span<BufferInfo>.Empty;
|
||||
|
||||
return Status.BadValue;
|
||||
}
|
||||
|
||||
lock (Core.Lock)
|
||||
{
|
||||
bufferHistoryCount = Math.Min(bufferHistoryCount, Core.BufferHistory.Length);
|
||||
|
||||
BufferInfo[] result = new BufferInfo[bufferHistoryCount];
|
||||
|
||||
uint position = Core.BufferHistoryPosition;
|
||||
|
||||
for (uint i = 0; i < bufferHistoryCount; i++)
|
||||
{
|
||||
result[i] = Core.BufferHistory[(position - i) % Core.BufferHistory.Length];
|
||||
|
||||
position--;
|
||||
}
|
||||
|
||||
bufferInfos = result;
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
Normal file
29
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class BufferSlot
|
||||
{
|
||||
public AndroidStrongPointer<GraphicBuffer> GraphicBuffer;
|
||||
public BufferState BufferState;
|
||||
public bool RequestBufferCalled;
|
||||
public ulong FrameNumber;
|
||||
public AndroidFence Fence;
|
||||
public bool AcquireCalled;
|
||||
public bool NeedsCleanupOnRelease;
|
||||
public bool AttachedByConsumer;
|
||||
public TimeSpanType QueueTime;
|
||||
public TimeSpanType PresentationTime;
|
||||
public bool IsPreallocated;
|
||||
|
||||
public BufferSlot()
|
||||
{
|
||||
GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
|
||||
BufferState = BufferState.Free;
|
||||
QueueTime = TimeSpanType.Zero;
|
||||
PresentationTime = TimeSpanType.Zero;
|
||||
IsPreallocated = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
175
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs
Normal file
175
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs
Normal file
|
@ -0,0 +1,175 @@
|
|||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class ConsumerBase : IConsumerListener
|
||||
{
|
||||
public class Slot
|
||||
{
|
||||
public AndroidStrongPointer<GraphicBuffer> GraphicBuffer;
|
||||
public AndroidFence Fence;
|
||||
public ulong FrameNumber;
|
||||
|
||||
public Slot()
|
||||
{
|
||||
GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
|
||||
}
|
||||
}
|
||||
|
||||
protected Slot[] Slots = new Slot[BufferSlotArray.NumBufferSlots];
|
||||
|
||||
protected bool IsAbandoned;
|
||||
|
||||
protected BufferQueueConsumer Consumer;
|
||||
|
||||
protected readonly object Lock = new object();
|
||||
|
||||
private IConsumerListener _listener;
|
||||
|
||||
public ConsumerBase(BufferQueueConsumer consumer, bool controlledByApp, IConsumerListener listener)
|
||||
{
|
||||
for (int i = 0; i < Slots.Length; i++)
|
||||
{
|
||||
Slots[i] = new Slot();
|
||||
}
|
||||
|
||||
IsAbandoned = false;
|
||||
Consumer = consumer;
|
||||
_listener = listener;
|
||||
|
||||
Status connectStatus = consumer.Connect(this, controlledByApp);
|
||||
|
||||
if (connectStatus != Status.Success)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnBuffersReleased()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
if (IsAbandoned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Consumer.GetReleasedBuffers(out ulong slotMask);
|
||||
|
||||
for (int i = 0; i < Slots.Length; i++)
|
||||
{
|
||||
if ((slotMask & (1UL << i)) != 0)
|
||||
{
|
||||
FreeBufferLocked(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnFrameAvailable(ref BufferItem item)
|
||||
{
|
||||
_listener?.OnFrameAvailable(ref item);
|
||||
}
|
||||
|
||||
public virtual void OnFrameReplaced(ref BufferItem item)
|
||||
{
|
||||
_listener?.OnFrameReplaced(ref item);
|
||||
}
|
||||
|
||||
protected virtual void FreeBufferLocked(int slotIndex)
|
||||
{
|
||||
Slots[slotIndex].GraphicBuffer.Reset();
|
||||
|
||||
Slots[slotIndex].Fence = AndroidFence.NoFence;
|
||||
Slots[slotIndex].FrameNumber = 0;
|
||||
}
|
||||
|
||||
public void Abandon()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
if (!IsAbandoned)
|
||||
{
|
||||
AbandonLocked();
|
||||
|
||||
IsAbandoned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AbandonLocked()
|
||||
{
|
||||
for (int i = 0; i < Slots.Length; i++)
|
||||
{
|
||||
FreeBufferLocked(i);
|
||||
}
|
||||
|
||||
Consumer.Disconnect();
|
||||
}
|
||||
|
||||
protected virtual Status AcquireBufferLocked(out BufferItem bufferItem, ulong expectedPresent)
|
||||
{
|
||||
Status acquireStatus = Consumer.AcquireBuffer(out bufferItem, expectedPresent);
|
||||
|
||||
if (acquireStatus != Status.Success)
|
||||
{
|
||||
return acquireStatus;
|
||||
}
|
||||
|
||||
if (!bufferItem.GraphicBuffer.IsNull)
|
||||
{
|
||||
Slots[bufferItem.Slot].GraphicBuffer.Set(bufferItem.GraphicBuffer.Object);
|
||||
}
|
||||
|
||||
Slots[bufferItem.Slot].FrameNumber = bufferItem.FrameNumber;
|
||||
Slots[bufferItem.Slot].Fence = bufferItem.Fence;
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
protected virtual Status AddReleaseFenceLocked(int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer, ref AndroidFence fence)
|
||||
{
|
||||
if (!StillTracking(slot, ref graphicBuffer))
|
||||
{
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
Slots[slot].Fence = fence;
|
||||
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
protected virtual Status ReleaseBufferLocked(int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer)
|
||||
{
|
||||
if (!StillTracking(slot, ref graphicBuffer))
|
||||
{
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
Status result = Consumer.ReleaseBuffer(slot, Slots[slot].FrameNumber, ref Slots[slot].Fence);
|
||||
|
||||
if (result == Status.StaleBufferSlot)
|
||||
{
|
||||
FreeBufferLocked(slot);
|
||||
}
|
||||
|
||||
Slots[slot].Fence = AndroidFence.NoFence;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual bool StillTracking(int slotIndex, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer)
|
||||
{
|
||||
if (slotIndex < 0 || slotIndex >= Slots.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Slot slot = Slots[slotIndex];
|
||||
|
||||
// TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be.
|
||||
return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == graphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class HOSBinderDriverServer : IHOSBinderDriver
|
||||
{
|
||||
private static Dictionary<int, IBinder> _registeredBinderObjects = new Dictionary<int, IBinder>();
|
||||
|
||||
private static int _lastBinderId = 0;
|
||||
|
||||
private static object _lock = new object();
|
||||
|
||||
public static int RegisterBinderObject(IBinder binder)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_lastBinderId++;
|
||||
|
||||
_registeredBinderObjects.Add(_lastBinderId, binder);
|
||||
|
||||
return _lastBinderId;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnregisterBinderObject(int binderId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_registeredBinderObjects.Remove(binderId);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetBinderId(IBinder binder)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (KeyValuePair<int, IBinder> pair in _registeredBinderObjects)
|
||||
{
|
||||
if (ReferenceEquals(binder, pair.Value))
|
||||
{
|
||||
return pair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static IBinder GetBinderObjectById(int binderId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_registeredBinderObjects.TryGetValue(binderId, out IBinder binder))
|
||||
{
|
||||
return binder;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override ResultCode AdjustRefcount(int binderId, int addVal, int type)
|
||||
{
|
||||
IBinder binder = GetBinderObjectById(binderId);
|
||||
|
||||
if (binder == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return binder.AdjustRefcount(addVal, type);
|
||||
}
|
||||
|
||||
protected override void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent)
|
||||
{
|
||||
IBinder binder = GetBinderObjectById(binderId);
|
||||
|
||||
if (binder == null)
|
||||
{
|
||||
readableEvent = null;
|
||||
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
binder.GetNativeHandle(typeId, out readableEvent);
|
||||
}
|
||||
|
||||
protected override ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel)
|
||||
{
|
||||
IBinder binder = GetBinderObjectById(binderId);
|
||||
|
||||
if (binder == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return binder.OnTransact(code, flags, inputParcel, outputParcel);
|
||||
}
|
||||
}
|
||||
}
|
41
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs
Normal file
41
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
interface IBinder
|
||||
{
|
||||
ResultCode AdjustRefcount(int addVal, int type);
|
||||
|
||||
void GetNativeHandle(uint typeId, out KReadableEvent readableEvent);
|
||||
|
||||
ResultCode OnTransact(uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel)
|
||||
{
|
||||
Parcel inputParcelReader = new Parcel(inputParcel.ToArray());
|
||||
|
||||
// TODO: support objects?
|
||||
Parcel outputParcelWriter = new Parcel((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0);
|
||||
|
||||
string inputInterfaceToken = inputParcelReader.ReadInterfaceToken();
|
||||
|
||||
if (!InterfaceToken.Equals(inputInterfaceToken))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid interface token {inputInterfaceToken} (expected: {InterfaceToken}");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
OnTransact(code, flags, inputParcelReader, outputParcelWriter);
|
||||
|
||||
outputParcelWriter.Finish().CopyTo(outputParcel);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel);
|
||||
|
||||
string InterfaceToken { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
interface IConsumerListener
|
||||
{
|
||||
void OnFrameAvailable(ref BufferItem item);
|
||||
void OnFrameReplaced(ref BufferItem item);
|
||||
void OnBuffersReleased();
|
||||
}
|
||||
}
|
13
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs
Normal file
13
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
interface IFlattenable
|
||||
{
|
||||
uint GetFlattenedSize();
|
||||
|
||||
uint GetFdCount();
|
||||
|
||||
void Flatten(Parcel parcel);
|
||||
|
||||
void Unflatten(Parcel parcel);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
abstract class IGraphicBufferProducer : IBinder
|
||||
{
|
||||
public string InterfaceToken => "android.gui.IGraphicBufferProducer";
|
||||
|
||||
enum TransactionCode : uint
|
||||
{
|
||||
RequestBuffer = 1,
|
||||
SetBufferCount,
|
||||
DequeueBuffer,
|
||||
DetachBuffer,
|
||||
DetachNextBuffer,
|
||||
AttachBuffer,
|
||||
QueueBuffer,
|
||||
CancelBuffer,
|
||||
Query,
|
||||
Connect,
|
||||
Disconnect,
|
||||
SetSidebandStream,
|
||||
AllocateBuffers,
|
||||
SetPreallocatedBuffer,
|
||||
Reserved15,
|
||||
GetBufferInfo,
|
||||
GetBufferHistory
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x54)]
|
||||
public struct QueueBufferInput : IFlattenable
|
||||
{
|
||||
public long Timestamp;
|
||||
public int IsAutoTimestamp;
|
||||
public Rect Crop;
|
||||
public NativeWindowScalingMode ScalingMode;
|
||||
public NativeWindowTransform Transform;
|
||||
public uint StickyTransform;
|
||||
public int Async;
|
||||
public int SwapInterval;
|
||||
public AndroidFence Fence;
|
||||
|
||||
public void Flatten(Parcel parcel)
|
||||
{
|
||||
parcel.WriteUnmanagedType(ref this);
|
||||
}
|
||||
|
||||
public uint GetFdCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint GetFlattenedSize()
|
||||
{
|
||||
return (uint)Unsafe.SizeOf<QueueBufferInput>();
|
||||
}
|
||||
|
||||
public void Unflatten(Parcel parcel)
|
||||
{
|
||||
this = parcel.ReadUnmanagedType<QueueBufferInput>();
|
||||
}
|
||||
}
|
||||
|
||||
public struct QueueBufferOutput
|
||||
{
|
||||
public uint Width;
|
||||
public uint Height;
|
||||
public NativeWindowTransform TransformHint;
|
||||
public uint NumPendingBuffers;
|
||||
public ulong FrameNumber;
|
||||
|
||||
public void WriteToParcel(Parcel parcel)
|
||||
{
|
||||
parcel.WriteUInt32(Width);
|
||||
parcel.WriteUInt32(Height);
|
||||
parcel.WriteUnmanagedType(ref TransformHint);
|
||||
parcel.WriteUInt32(NumPendingBuffers);
|
||||
|
||||
if (TransformHint.HasFlag(NativeWindowTransform.ReturnFrameNumber))
|
||||
{
|
||||
parcel.WriteUInt64(FrameNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode AdjustRefcount(int addVal, int type)
|
||||
{
|
||||
// TODO?
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public void GetNativeHandle(uint typeId, out KReadableEvent readableEvent)
|
||||
{
|
||||
if (typeId == 0xF)
|
||||
{
|
||||
readableEvent = GetWaitBufferFreeEvent();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException($"Unimplemented native event type {typeId}!");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel)
|
||||
{
|
||||
Status status = Status.Success;
|
||||
int slot;
|
||||
AndroidFence fence;
|
||||
QueueBufferInput queueInput;
|
||||
QueueBufferOutput queueOutput;
|
||||
NativeWindowApi api;
|
||||
|
||||
AndroidStrongPointer<GraphicBuffer> graphicBuffer;
|
||||
AndroidStrongPointer<AndroidFence> strongFence;
|
||||
|
||||
switch ((TransactionCode)code)
|
||||
{
|
||||
case TransactionCode.RequestBuffer:
|
||||
slot = inputParcel.ReadInt32();
|
||||
|
||||
status = RequestBuffer(slot, out graphicBuffer);
|
||||
|
||||
outputParcel.WriteStrongPointer(ref graphicBuffer);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.SetBufferCount:
|
||||
int bufferCount = inputParcel.ReadInt32();
|
||||
|
||||
status = SetBufferCount(bufferCount);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.DequeueBuffer:
|
||||
bool async = inputParcel.ReadBoolean();
|
||||
uint width = inputParcel.ReadUInt32();
|
||||
uint height = inputParcel.ReadUInt32();
|
||||
PixelFormat format = inputParcel.ReadUnmanagedType<PixelFormat>();
|
||||
uint usage = inputParcel.ReadUInt32();
|
||||
|
||||
status = DequeueBuffer(out int dequeueSlot, out fence, async, width, height, format, usage);
|
||||
strongFence = new AndroidStrongPointer<AndroidFence>(fence);
|
||||
|
||||
outputParcel.WriteInt32(dequeueSlot);
|
||||
outputParcel.WriteStrongPointer(ref strongFence);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.DetachBuffer:
|
||||
slot = inputParcel.ReadInt32();
|
||||
|
||||
status = DetachBuffer(slot);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.DetachNextBuffer:
|
||||
status = DetachNextBuffer(out graphicBuffer, out fence);
|
||||
strongFence = new AndroidStrongPointer<AndroidFence>(fence);
|
||||
|
||||
outputParcel.WriteStrongPointer(ref graphicBuffer);
|
||||
outputParcel.WriteStrongPointer(ref strongFence);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.AttachBuffer:
|
||||
graphicBuffer = inputParcel.ReadStrongPointer<GraphicBuffer>();
|
||||
|
||||
status = AttachBuffer(out slot, graphicBuffer);
|
||||
|
||||
outputParcel.WriteInt32(slot);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.QueueBuffer:
|
||||
slot = inputParcel.ReadInt32();
|
||||
queueInput = inputParcel.ReadFlattenable<QueueBufferInput>();
|
||||
|
||||
status = QueueBuffer(slot, ref queueInput, out queueOutput);
|
||||
|
||||
queueOutput.WriteToParcel(outputParcel);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.CancelBuffer:
|
||||
slot = inputParcel.ReadInt32();
|
||||
fence = inputParcel.ReadFlattenable<AndroidFence>();
|
||||
|
||||
CancelBuffer(slot, ref fence);
|
||||
|
||||
outputParcel.WriteStatus(Status.Success);
|
||||
|
||||
break;
|
||||
case TransactionCode.Query:
|
||||
NativeWindowAttribute what = inputParcel.ReadUnmanagedType<NativeWindowAttribute>();
|
||||
|
||||
status = Query(what, out int outValue);
|
||||
|
||||
outputParcel.WriteInt32(outValue);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.Connect:
|
||||
bool hasListener = inputParcel.ReadBoolean();
|
||||
|
||||
IProducerListener listener = null;
|
||||
|
||||
if (hasListener)
|
||||
{
|
||||
throw new NotImplementedException("Connect with a strong binder listener isn't implemented");
|
||||
}
|
||||
|
||||
api = inputParcel.ReadUnmanagedType<NativeWindowApi>();
|
||||
|
||||
bool producerControlledByApp = inputParcel.ReadBoolean();
|
||||
|
||||
status = Connect(listener, api, producerControlledByApp, out queueOutput);
|
||||
|
||||
queueOutput.WriteToParcel(outputParcel);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.Disconnect:
|
||||
api = inputParcel.ReadUnmanagedType<NativeWindowApi>();
|
||||
|
||||
status = Disconnect(api);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.SetPreallocatedBuffer:
|
||||
slot = inputParcel.ReadInt32();
|
||||
|
||||
graphicBuffer = inputParcel.ReadStrongPointer<GraphicBuffer>();
|
||||
|
||||
status = SetPreallocatedBuffer(slot, graphicBuffer);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
break;
|
||||
case TransactionCode.GetBufferHistory:
|
||||
int bufferHistoryCount = inputParcel.ReadInt32();
|
||||
|
||||
status = GetBufferHistory(bufferHistoryCount, out Span<BufferInfo> bufferInfos);
|
||||
|
||||
outputParcel.WriteStatus(status);
|
||||
|
||||
outputParcel.WriteInt32(bufferInfos.Length);
|
||||
|
||||
outputParcel.WriteUnmanagedSpan<BufferInfo>(bufferInfos);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Transaction {(TransactionCode)code} not implemented");
|
||||
}
|
||||
|
||||
if (status != Status.Success)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Error returned by transaction {(TransactionCode)code}: {status}");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract KReadableEvent GetWaitBufferFreeEvent();
|
||||
|
||||
public abstract Status RequestBuffer(int slot, out AndroidStrongPointer<GraphicBuffer> graphicBuffer);
|
||||
|
||||
public abstract Status SetBufferCount(int bufferCount);
|
||||
|
||||
public abstract Status DequeueBuffer(out int slot, out AndroidFence fence, bool async, uint width, uint height, PixelFormat format, uint usage);
|
||||
|
||||
public abstract Status DetachBuffer(int slot);
|
||||
|
||||
public abstract Status DetachNextBuffer(out AndroidStrongPointer<GraphicBuffer> graphicBuffer, out AndroidFence fence);
|
||||
|
||||
public abstract Status AttachBuffer(out int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer);
|
||||
|
||||
public abstract Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output);
|
||||
|
||||
public abstract void CancelBuffer(int slot, ref AndroidFence fence);
|
||||
|
||||
public abstract Status Query(NativeWindowAttribute what, out int outValue);
|
||||
|
||||
public abstract Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output);
|
||||
|
||||
public abstract Status Disconnect(NativeWindowApi api);
|
||||
|
||||
public abstract Status SetPreallocatedBuffer(int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer);
|
||||
|
||||
public abstract Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos);
|
||||
}
|
||||
}
|
109
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
Normal file
109
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs
Normal file
|
@ -0,0 +1,109 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
abstract class IHOSBinderDriver : IpcService
|
||||
{
|
||||
public IHOSBinderDriver() { }
|
||||
|
||||
[CommandCmif(0)]
|
||||
// TransactParcel(s32, u32, u32, buffer<unknown, 5, 0>) -> buffer<unknown, 6, 0>
|
||||
public ResultCode TransactParcel(ServiceCtx context)
|
||||
{
|
||||
int binderId = context.RequestData.ReadInt32();
|
||||
|
||||
uint code = context.RequestData.ReadUInt32();
|
||||
uint flags = context.RequestData.ReadUInt32();
|
||||
|
||||
ulong dataPos = context.Request.SendBuff[0].Position;
|
||||
ulong dataSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
ulong replyPos = context.Request.ReceiveBuff[0].Position;
|
||||
ulong replySize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
|
||||
|
||||
Span<byte> outputParcel = new Span<byte>(new byte[replySize]);
|
||||
|
||||
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.Memory.Write(replyPos, outputParcel);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// AdjustRefcount(s32, s32, s32)
|
||||
public ResultCode AdjustRefcount(ServiceCtx context)
|
||||
{
|
||||
int binderId = context.RequestData.ReadInt32();
|
||||
int addVal = context.RequestData.ReadInt32();
|
||||
int type = context.RequestData.ReadInt32();
|
||||
|
||||
return AdjustRefcount(binderId, addVal, type);
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetNativeHandle(s32, s32) -> handle<copy>
|
||||
public ResultCode GetNativeHandle(ServiceCtx context)
|
||||
{
|
||||
int binderId = context.RequestData.ReadInt32();
|
||||
|
||||
uint typeId = context.RequestData.ReadUInt32();
|
||||
|
||||
GetNativeHandle(binderId, typeId, out KReadableEvent readableEvent);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(readableEvent, out int handle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)] // 3.0.0+
|
||||
// TransactParcelAuto(s32, u32, u32, buffer<unknown, 21, 0>) -> buffer<unknown, 22, 0>
|
||||
public ResultCode TransactParcelAuto(ServiceCtx context)
|
||||
{
|
||||
int binderId = context.RequestData.ReadInt32();
|
||||
|
||||
uint code = context.RequestData.ReadUInt32();
|
||||
uint flags = context.RequestData.ReadUInt32();
|
||||
|
||||
(ulong dataPos, ulong dataSize) = context.Request.GetBufferType0x21();
|
||||
(ulong replyPos, ulong replySize) = context.Request.GetBufferType0x22();
|
||||
|
||||
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
|
||||
|
||||
using (IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.Shared.RentCleared(replySize))
|
||||
{
|
||||
Span<byte> outputParcel = outputParcelOwner.Memory.Span;
|
||||
|
||||
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.Memory.Write(replyPos, outputParcel);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ResultCode AdjustRefcount(int binderId, int addVal, int type);
|
||||
|
||||
protected abstract void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent);
|
||||
|
||||
protected abstract ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
interface IProducerListener
|
||||
{
|
||||
void OnBufferReleased();
|
||||
}
|
||||
}
|
10
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs
Normal file
10
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum LayerState
|
||||
{
|
||||
NotInitialized,
|
||||
ManagedClosed,
|
||||
ManagedOpened,
|
||||
Stray
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum NativeWindowApi : int
|
||||
{
|
||||
NoApi = 0,
|
||||
NVN = 1,
|
||||
CPU = 2,
|
||||
Media = 3,
|
||||
Camera = 4
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum NativeWindowScalingMode : uint
|
||||
{
|
||||
Freeze = 0,
|
||||
ScaleToWindow = 1,
|
||||
ScaleCrop = 2,
|
||||
Unknown = 3,
|
||||
NoScaleCrop = 4,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[Flags]
|
||||
enum NativeWindowTransform : uint
|
||||
{
|
||||
None = 0,
|
||||
FlipX = 1,
|
||||
FlipY = 2,
|
||||
Rotate90 = 4,
|
||||
Rotate180 = FlipX | FlipY,
|
||||
Rotate270 = Rotate90 | Rotate180,
|
||||
InverseDisplay = 8,
|
||||
NoVSyncCapability = 0x10,
|
||||
ReturnFrameNumber = 0x20
|
||||
}
|
||||
}
|
221
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
Normal file
221
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
Normal file
|
@ -0,0 +1,221 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class Parcel
|
||||
{
|
||||
private readonly byte[] _rawData;
|
||||
|
||||
private Span<byte> Raw => new Span<byte>(_rawData);
|
||||
|
||||
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(_rawData)[0];
|
||||
|
||||
private Span<byte> Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize);
|
||||
|
||||
private Span<byte> Objects => Raw.Slice((int)Header.ObjectOffset, (int)Header.ObjectsSize);
|
||||
|
||||
private int _payloadPosition;
|
||||
private int _objectPosition;
|
||||
|
||||
public Parcel(byte[] rawData)
|
||||
{
|
||||
_rawData = rawData;
|
||||
|
||||
_payloadPosition = 0;
|
||||
_objectPosition = 0;
|
||||
}
|
||||
|
||||
public Parcel(uint payloadSize, uint objectsSize)
|
||||
{
|
||||
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
|
||||
|
||||
_rawData = new byte[BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)];
|
||||
|
||||
Header.PayloadSize = payloadSize;
|
||||
Header.ObjectsSize = objectsSize;
|
||||
Header.PayloadOffset = headerSize;
|
||||
Header.ObjectOffset = Header.PayloadOffset + Header.ObjectsSize;
|
||||
}
|
||||
|
||||
public string ReadInterfaceToken()
|
||||
{
|
||||
// Ignore the policy flags
|
||||
int strictPolicy = ReadInt32();
|
||||
|
||||
return ReadString16();
|
||||
}
|
||||
|
||||
public string ReadString16()
|
||||
{
|
||||
int size = ReadInt32();
|
||||
|
||||
if (size < 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> data = ReadInPlace((size + 1) * 2);
|
||||
|
||||
// Return the unicode string without the last character (null terminator)
|
||||
return Encoding.Unicode.GetString(data.Slice(0, size * 2));
|
||||
}
|
||||
|
||||
public int ReadInt32() => ReadUnmanagedType<int>();
|
||||
public uint ReadUInt32() => ReadUnmanagedType<uint>();
|
||||
public bool ReadBoolean() => ReadUnmanagedType<uint>() != 0;
|
||||
public long ReadInt64() => ReadUnmanagedType<long>();
|
||||
public ulong ReadUInt64() => ReadUnmanagedType<ulong>();
|
||||
|
||||
public T ReadFlattenable<T>() where T : unmanaged, IFlattenable
|
||||
{
|
||||
long flattenableSize = ReadInt64();
|
||||
|
||||
T result = new T();
|
||||
|
||||
Debug.Assert(flattenableSize == result.GetFlattenedSize());
|
||||
|
||||
result.Unflatten(this);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public T ReadUnmanagedType<T>() where T: unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> data = ReadInPlace(Unsafe.SizeOf<T>());
|
||||
|
||||
return MemoryMarshal.Cast<byte, T>(data)[0];
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> ReadInPlace(int size)
|
||||
{
|
||||
ReadOnlySpan<byte> result = Payload.Slice(_payloadPosition, size);
|
||||
|
||||
_payloadPosition += BitUtils.AlignUp(size, 4);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
|
||||
private struct FlatBinderObject
|
||||
{
|
||||
public int Type;
|
||||
public int Flags;
|
||||
public long BinderId;
|
||||
public long Cookie;
|
||||
|
||||
private byte _serviceNameStart;
|
||||
|
||||
public Span<byte> ServiceName => MemoryMarshal.CreateSpan(ref _serviceNameStart, 0x8);
|
||||
}
|
||||
|
||||
public void WriteObject<T>(T obj, string serviceName) where T: IBinder
|
||||
{
|
||||
FlatBinderObject flatBinderObject = new FlatBinderObject
|
||||
{
|
||||
Type = 2,
|
||||
Flags = 0,
|
||||
BinderId = HOSBinderDriverServer.GetBinderId(obj),
|
||||
};
|
||||
|
||||
Encoding.ASCII.GetBytes(serviceName).CopyTo(flatBinderObject.ServiceName);
|
||||
|
||||
WriteUnmanagedType(ref flatBinderObject);
|
||||
|
||||
// TODO: figure out what this value is
|
||||
|
||||
WriteInplaceObject(new byte[4] { 0, 0, 0, 0 });
|
||||
}
|
||||
|
||||
public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable
|
||||
{
|
||||
bool hasObject = ReadBoolean();
|
||||
|
||||
if (hasObject)
|
||||
{
|
||||
T obj = ReadFlattenable<T>();
|
||||
|
||||
return new AndroidStrongPointer<T>(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AndroidStrongPointer<T>();
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteStrongPointer<T>(ref AndroidStrongPointer<T> value) where T: unmanaged, IFlattenable
|
||||
{
|
||||
WriteBoolean(!value.IsNull);
|
||||
|
||||
if (!value.IsNull)
|
||||
{
|
||||
WriteFlattenable<T>(ref value.Object);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFlattenable<T>(ref T value) where T : unmanaged, IFlattenable
|
||||
{
|
||||
WriteInt64(value.GetFlattenedSize());
|
||||
|
||||
value.Flatten(this);
|
||||
}
|
||||
|
||||
public void WriteStatus(Status status) => WriteUnmanagedType(ref status);
|
||||
public void WriteBoolean(bool value) => WriteUnmanagedType(ref value);
|
||||
public void WriteInt32(int value) => WriteUnmanagedType(ref value);
|
||||
public void WriteUInt32(uint value) => WriteUnmanagedType(ref value);
|
||||
public void WriteInt64(long value) => WriteUnmanagedType(ref value);
|
||||
public void WriteUInt64(ulong value) => WriteUnmanagedType(ref value);
|
||||
|
||||
public void WriteUnmanagedSpan<T>(ReadOnlySpan<T> value) where T : unmanaged
|
||||
{
|
||||
WriteInplace(MemoryMarshal.Cast<T, byte>(value));
|
||||
}
|
||||
|
||||
public void WriteUnmanagedType<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
WriteInplace(SpanHelpers.AsByteSpan(ref value));
|
||||
}
|
||||
|
||||
public void WriteInplace(ReadOnlySpan<byte> data)
|
||||
{
|
||||
Span<byte> result = Payload.Slice(_payloadPosition, data.Length);
|
||||
|
||||
data.CopyTo(result);
|
||||
|
||||
_payloadPosition += BitUtils.AlignUp(data.Length, 4);
|
||||
}
|
||||
|
||||
public void WriteInplaceObject(ReadOnlySpan<byte> data)
|
||||
{
|
||||
Span<byte> result = Objects.Slice(_objectPosition, data.Length);
|
||||
|
||||
data.CopyTo(result);
|
||||
|
||||
_objectPosition += BitUtils.AlignUp(data.Length, 4);
|
||||
}
|
||||
|
||||
private void UpdateHeader()
|
||||
{
|
||||
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
|
||||
|
||||
Header.PayloadSize = (uint)_payloadPosition;
|
||||
Header.ObjectsSize = (uint)_objectPosition;
|
||||
Header.PayloadOffset = headerSize;
|
||||
Header.ObjectOffset = Header.PayloadOffset + Header.PayloadSize;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Finish()
|
||||
{
|
||||
UpdateHeader();
|
||||
|
||||
return Raw.Slice(0, (int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf<ParcelHeader>()));
|
||||
}
|
||||
}
|
||||
}
|
10
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs
Normal file
10
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
struct ParcelHeader
|
||||
{
|
||||
public uint PayloadSize;
|
||||
public uint PayloadOffset;
|
||||
public uint ObjectsSize;
|
||||
public uint ObjectOffset;
|
||||
}
|
||||
}
|
14
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs
Normal file
14
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum PixelFormat : uint
|
||||
{
|
||||
Unknown,
|
||||
Rgba8888,
|
||||
Rgbx8888,
|
||||
Rgb888,
|
||||
Rgb565,
|
||||
Bgra8888,
|
||||
Rgba5551,
|
||||
Rgba4444,
|
||||
}
|
||||
}
|
22
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs
Normal file
22
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
548
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
Normal file
548
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
Normal file
|
@ -0,0 +1,548 @@
|
|||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
using ResultCode = Ryujinx.HLE.HOS.Services.Vi.ResultCode;
|
||||
|
||||
class SurfaceFlinger : IConsumerListener, IDisposable
|
||||
{
|
||||
private const int TargetFps = 60;
|
||||
|
||||
private Switch _device;
|
||||
|
||||
private Dictionary<long, Layer> _layers;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
private Thread _composerThread;
|
||||
|
||||
private Stopwatch _chrono;
|
||||
|
||||
private ManualResetEvent _event = new ManualResetEvent(false);
|
||||
private AutoResetEvent _nextFrameEvent = new AutoResetEvent(true);
|
||||
private long _ticks;
|
||||
private long _ticksPerFrame;
|
||||
private long _spinTicks;
|
||||
private long _1msTicks;
|
||||
|
||||
private int _swapInterval;
|
||||
private int _swapIntervalDelay;
|
||||
|
||||
private readonly object Lock = new object();
|
||||
|
||||
public long RenderLayerId { get; private set; }
|
||||
|
||||
private class Layer
|
||||
{
|
||||
public int ProducerBinderId;
|
||||
public IGraphicBufferProducer Producer;
|
||||
public BufferItemConsumer Consumer;
|
||||
public BufferQueueCore Core;
|
||||
public ulong Owner;
|
||||
public LayerState State;
|
||||
}
|
||||
|
||||
private class TextureCallbackInformation
|
||||
{
|
||||
public Layer Layer;
|
||||
public BufferItem Item;
|
||||
}
|
||||
|
||||
public SurfaceFlinger(Switch device)
|
||||
{
|
||||
_device = device;
|
||||
_layers = new Dictionary<long, Layer>();
|
||||
RenderLayerId = 0;
|
||||
|
||||
_composerThread = new Thread(HandleComposition)
|
||||
{
|
||||
Name = "SurfaceFlinger.Composer"
|
||||
};
|
||||
|
||||
_chrono = new Stopwatch();
|
||||
_chrono.Start();
|
||||
|
||||
_ticks = 0;
|
||||
_spinTicks = Stopwatch.Frequency / 500;
|
||||
_1msTicks = Stopwatch.Frequency / 1000;
|
||||
|
||||
UpdateSwapInterval(1);
|
||||
|
||||
_composerThread.Start();
|
||||
}
|
||||
|
||||
private void UpdateSwapInterval(int swapInterval)
|
||||
{
|
||||
_swapInterval = swapInterval;
|
||||
|
||||
// If the swap interval is 0, Game VSync is disabled.
|
||||
if (_swapInterval == 0)
|
||||
{
|
||||
_nextFrameEvent.Set();
|
||||
_ticksPerFrame = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||
}
|
||||
}
|
||||
|
||||
public IGraphicBufferProducer CreateLayer(out long layerId, ulong pid, LayerState initialState = LayerState.ManagedClosed)
|
||||
{
|
||||
layerId = 1;
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
foreach (KeyValuePair<long, Layer> pair in _layers)
|
||||
{
|
||||
if (pair.Key >= layerId)
|
||||
{
|
||||
layerId = pair.Key + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CreateLayerFromId(pid, layerId, initialState);
|
||||
|
||||
return GetProducerByLayerId(layerId);
|
||||
}
|
||||
|
||||
private void CreateLayerFromId(ulong pid, long layerId, LayerState initialState)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.SurfaceFlinger, $"Creating layer {layerId}");
|
||||
|
||||
BufferQueueCore core = BufferQueue.CreateBufferQueue(_device, pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer);
|
||||
|
||||
core.BufferQueued += () =>
|
||||
{
|
||||
_nextFrameEvent.Set();
|
||||
};
|
||||
|
||||
_layers.Add(layerId, new Layer
|
||||
{
|
||||
ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer),
|
||||
Producer = producer,
|
||||
Consumer = new BufferItemConsumer(_device, consumer, 0, -1, false, this),
|
||||
Core = core,
|
||||
Owner = pid,
|
||||
State = initialState
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode OpenLayer(ulong pid, long layerId, out IBinder producer)
|
||||
{
|
||||
Layer layer = GetLayerByIdLocked(layerId);
|
||||
|
||||
if (layer == null || layer.State != LayerState.ManagedClosed)
|
||||
{
|
||||
producer = null;
|
||||
|
||||
return ResultCode.InvalidArguments;
|
||||
}
|
||||
|
||||
layer.State = LayerState.ManagedOpened;
|
||||
producer = layer.Producer;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode CloseLayer(long layerId)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
Layer layer = GetLayerByIdLocked(layerId);
|
||||
|
||||
if (layer == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to close layer {layerId}");
|
||||
|
||||
return ResultCode.InvalidValue;
|
||||
}
|
||||
|
||||
CloseLayer(layerId, layer);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode DestroyManagedLayer(long layerId)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
Layer layer = GetLayerByIdLocked(layerId);
|
||||
|
||||
if (layer == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (not found)");
|
||||
|
||||
return ResultCode.InvalidValue;
|
||||
}
|
||||
|
||||
if (layer.State != LayerState.ManagedClosed && layer.State != LayerState.ManagedOpened)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (permission denied)");
|
||||
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
|
||||
|
||||
if (_layers.Remove(layerId) && layer.State == LayerState.ManagedOpened)
|
||||
{
|
||||
CloseLayer(layerId, layer);
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode DestroyStrayLayer(long layerId)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
Layer layer = GetLayerByIdLocked(layerId);
|
||||
|
||||
if (layer == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (not found)");
|
||||
|
||||
return ResultCode.InvalidValue;
|
||||
}
|
||||
|
||||
if (layer.State != LayerState.Stray)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (permission denied)");
|
||||
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
|
||||
|
||||
if (_layers.Remove(layerId))
|
||||
{
|
||||
CloseLayer(layerId, layer);
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseLayer(long layerId, Layer layer)
|
||||
{
|
||||
// If the layer was removed and the current in use, we need to change the current layer in use.
|
||||
if (RenderLayerId == layerId)
|
||||
{
|
||||
// If no layer is availaible, reset to default value.
|
||||
if (_layers.Count == 0)
|
||||
{
|
||||
SetRenderLayer(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRenderLayer(_layers.Last().Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (layer.State == LayerState.ManagedOpened)
|
||||
{
|
||||
layer.State = LayerState.ManagedClosed;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderLayer(long layerId)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
RenderLayerId = layerId;
|
||||
}
|
||||
}
|
||||
|
||||
private Layer GetLayerByIdLocked(long layerId)
|
||||
{
|
||||
foreach (KeyValuePair<long, Layer> pair in _layers)
|
||||
{
|
||||
if (pair.Key == layerId)
|
||||
{
|
||||
return pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IGraphicBufferProducer GetProducerByLayerId(long layerId)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
Layer layer = GetLayerByIdLocked(layerId);
|
||||
|
||||
if (layer != null)
|
||||
{
|
||||
return layer.Producer;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleComposition()
|
||||
{
|
||||
_isRunning = true;
|
||||
|
||||
long lastTicks = _chrono.ElapsedTicks;
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
long ticks = _chrono.ElapsedTicks;
|
||||
|
||||
if (_swapInterval == 0)
|
||||
{
|
||||
Compose();
|
||||
|
||||
_device.System?.SignalVsync();
|
||||
|
||||
_nextFrameEvent.WaitOne(17);
|
||||
lastTicks = ticks;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ticks += ticks - lastTicks;
|
||||
lastTicks = ticks;
|
||||
|
||||
if (_ticks >= _ticksPerFrame)
|
||||
{
|
||||
if (_swapIntervalDelay-- == 0)
|
||||
{
|
||||
Compose();
|
||||
|
||||
// When a frame is presented, delay the next one by its swap interval value.
|
||||
_swapIntervalDelay = Math.Max(0, _swapInterval - 1);
|
||||
}
|
||||
|
||||
_device.System?.SignalVsync();
|
||||
|
||||
// Apply a maximum bound of 3 frames to the tick remainder, in case some event causes Ryujinx to pause for a long time or messes with the timer.
|
||||
_ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame * 3);
|
||||
}
|
||||
|
||||
// Sleep if possible. If the time til the next frame is too low, spin wait instead.
|
||||
long diff = _ticksPerFrame - (_ticks + _chrono.ElapsedTicks - ticks);
|
||||
if (diff > 0)
|
||||
{
|
||||
if (diff < _spinTicks)
|
||||
{
|
||||
do
|
||||
{
|
||||
// SpinWait is a little more HT/SMT friendly than aggressively updating/checking ticks.
|
||||
// The value of 5 still gives us quite a bit of precision (~0.0003ms variance at worst) while waiting a reasonable amount of time.
|
||||
Thread.SpinWait(5);
|
||||
|
||||
ticks = _chrono.ElapsedTicks;
|
||||
_ticks += ticks - lastTicks;
|
||||
lastTicks = ticks;
|
||||
} while (_ticks < _ticksPerFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
_event.WaitOne((int)(diff / _1msTicks));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Compose()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
// TODO: support multilayers (& multidisplay ?)
|
||||
if (RenderLayerId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Layer layer = GetLayerByIdLocked(RenderLayerId);
|
||||
|
||||
Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
|
||||
|
||||
if (acquireStatus == Status.Success)
|
||||
{
|
||||
// If device vsync is disabled, reflect the change.
|
||||
if (!_device.EnableDeviceVsync)
|
||||
{
|
||||
if (_swapInterval != 0)
|
||||
{
|
||||
UpdateSwapInterval(0);
|
||||
}
|
||||
}
|
||||
else if (item.SwapInterval != _swapInterval)
|
||||
{
|
||||
UpdateSwapInterval(item.SwapInterval);
|
||||
}
|
||||
|
||||
PostFrameBuffer(layer, item);
|
||||
}
|
||||
else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PostFrameBuffer(Layer layer, BufferItem item)
|
||||
{
|
||||
int frameBufferWidth = item.GraphicBuffer.Object.Width;
|
||||
int frameBufferHeight = item.GraphicBuffer.Object.Height;
|
||||
|
||||
int nvMapHandle = item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
|
||||
|
||||
if (nvMapHandle == 0)
|
||||
{
|
||||
nvMapHandle = item.GraphicBuffer.Object.Buffer.NvMapId;
|
||||
}
|
||||
|
||||
ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle);
|
||||
|
||||
ulong frameBufferAddress = map.Address + bufferOffset;
|
||||
|
||||
Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
|
||||
|
||||
int bytesPerPixel =
|
||||
format == Format.B5G6R5Unorm ||
|
||||
format == Format.R4G4B4A4Unorm ? 2 : 4;
|
||||
|
||||
int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2;
|
||||
|
||||
// Note: Rotation is being ignored.
|
||||
Rect cropRect = item.Crop;
|
||||
|
||||
bool flipX = item.Transform.HasFlag(NativeWindowTransform.FlipX);
|
||||
bool flipY = item.Transform.HasFlag(NativeWindowTransform.FlipY);
|
||||
|
||||
AspectRatio aspectRatio = _device.Configuration.AspectRatio;
|
||||
bool isStretched = aspectRatio == AspectRatio.Stretched;
|
||||
|
||||
ImageCrop crop = new ImageCrop(
|
||||
cropRect.Left,
|
||||
cropRect.Right,
|
||||
cropRect.Top,
|
||||
cropRect.Bottom,
|
||||
flipX,
|
||||
flipY,
|
||||
isStretched,
|
||||
aspectRatio.ToFloatX(),
|
||||
aspectRatio.ToFloatY());
|
||||
|
||||
TextureCallbackInformation textureCallbackInformation = new TextureCallbackInformation
|
||||
{
|
||||
Layer = layer,
|
||||
Item = item
|
||||
};
|
||||
|
||||
if (_device.Gpu.Window.EnqueueFrameThreadSafe(
|
||||
layer.Owner,
|
||||
frameBufferAddress,
|
||||
frameBufferWidth,
|
||||
frameBufferHeight,
|
||||
0,
|
||||
false,
|
||||
gobBlocksInY,
|
||||
format,
|
||||
bytesPerPixel,
|
||||
crop,
|
||||
AcquireBuffer,
|
||||
ReleaseBuffer,
|
||||
textureCallbackInformation))
|
||||
{
|
||||
if (item.Fence.FenceCount == 0)
|
||||
{
|
||||
_device.Gpu.Window.SignalFrameReady();
|
||||
_device.Gpu.GPFifo.Interrupt();
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Fence.RegisterCallback(_device.Gpu, (x) =>
|
||||
{
|
||||
_device.Gpu.Window.SignalFrameReady();
|
||||
_device.Gpu.GPFifo.Interrupt();
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ReleaseBuffer(textureCallbackInformation);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseBuffer(object obj)
|
||||
{
|
||||
ReleaseBuffer((TextureCallbackInformation)obj);
|
||||
}
|
||||
|
||||
private void ReleaseBuffer(TextureCallbackInformation information)
|
||||
{
|
||||
AndroidFence fence = AndroidFence.NoFence;
|
||||
|
||||
information.Layer.Consumer.ReleaseBuffer(information.Item, ref fence);
|
||||
}
|
||||
|
||||
private void AcquireBuffer(GpuContext ignored, object obj)
|
||||
{
|
||||
AcquireBuffer((TextureCallbackInformation)obj);
|
||||
}
|
||||
|
||||
private void AcquireBuffer(TextureCallbackInformation information)
|
||||
{
|
||||
information.Item.Fence.WaitForever(_device.Gpu);
|
||||
}
|
||||
|
||||
public static Format ConvertColorFormat(ColorFormat colorFormat)
|
||||
{
|
||||
return colorFormat switch
|
||||
{
|
||||
ColorFormat.A8B8G8R8 => Format.R8G8B8A8Unorm,
|
||||
ColorFormat.X8B8G8R8 => Format.R8G8B8A8Unorm,
|
||||
ColorFormat.R5G6B5 => Format.B5G6R5Unorm,
|
||||
ColorFormat.A8R8G8B8 => Format.B8G8R8A8Unorm,
|
||||
ColorFormat.A4B4G4R4 => Format.R4G4B4A4Unorm,
|
||||
_ => throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"),
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isRunning = false;
|
||||
|
||||
foreach (Layer layer in _layers.Values)
|
||||
{
|
||||
layer.Core.PrepareForExit();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnFrameAvailable(ref BufferItem item)
|
||||
{
|
||||
_device.Statistics.RecordGameFrameTime();
|
||||
}
|
||||
|
||||
public void OnFrameReplaced(ref BufferItem item)
|
||||
{
|
||||
_device.Statistics.RecordGameFrameTime();
|
||||
}
|
||||
|
||||
public void OnBuffersReleased() {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.Types;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)]
|
||||
struct AndroidFence : IFlattenable
|
||||
{
|
||||
public int FenceCount;
|
||||
|
||||
private byte _fenceStorageStart;
|
||||
|
||||
private Span<byte> _storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf<NvFence>() * 4);
|
||||
|
||||
public Span<NvFence> NvFences => MemoryMarshal.Cast<byte, NvFence>(_storage);
|
||||
|
||||
public static AndroidFence NoFence
|
||||
{
|
||||
get
|
||||
{
|
||||
AndroidFence fence = new AndroidFence
|
||||
{
|
||||
FenceCount = 0
|
||||
};
|
||||
|
||||
fence.NvFences[0].Id = NvFence.InvalidSyncPointId;
|
||||
|
||||
return fence;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFence(NvFence fence)
|
||||
{
|
||||
NvFences[FenceCount++] = fence;
|
||||
}
|
||||
|
||||
public void WaitForever(GpuContext gpuContext)
|
||||
{
|
||||
bool hasTimeout = Wait(gpuContext, TimeSpan.FromMilliseconds(3000));
|
||||
|
||||
if (hasTimeout)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.SurfaceFlinger, "Android fence didn't signal in 3000 ms");
|
||||
Wait(gpuContext, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool Wait(GpuContext gpuContext, TimeSpan timeout)
|
||||
{
|
||||
for (int i = 0; i < FenceCount; i++)
|
||||
{
|
||||
bool hasTimeout = NvFences[i].Wait(gpuContext, timeout);
|
||||
|
||||
if (hasTimeout)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RegisterCallback(GpuContext gpuContext, Action<SyncpointWaiterHandle> callback)
|
||||
{
|
||||
ref NvFence fence = ref NvFences[FenceCount - 1];
|
||||
|
||||
if (fence.IsValid())
|
||||
{
|
||||
gpuContext.Synchronization.RegisterCallbackOnSyncpoint(fence.Id, fence.Value, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetFlattenedSize()
|
||||
{
|
||||
return (uint)Unsafe.SizeOf<AndroidFence>();
|
||||
}
|
||||
|
||||
public uint GetFdCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Flatten(Parcel parcel)
|
||||
{
|
||||
parcel.WriteUnmanagedType(ref this);
|
||||
}
|
||||
|
||||
public void Unflatten(Parcel parcel)
|
||||
{
|
||||
this = parcel.ReadUnmanagedType<AndroidFence>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types
|
||||
{
|
||||
class AndroidStrongPointer<T> where T: unmanaged, IFlattenable
|
||||
{
|
||||
public T Object;
|
||||
|
||||
private bool _hasObject;
|
||||
|
||||
public bool IsNull => !_hasObject;
|
||||
|
||||
public AndroidStrongPointer()
|
||||
{
|
||||
_hasObject = false;
|
||||
}
|
||||
|
||||
public AndroidStrongPointer(T obj)
|
||||
{
|
||||
Set(obj);
|
||||
}
|
||||
|
||||
public void Set(AndroidStrongPointer<T> other)
|
||||
{
|
||||
Object = other.Object;
|
||||
_hasObject = other._hasObject;
|
||||
}
|
||||
|
||||
public void Set(T obj)
|
||||
{
|
||||
Object = obj;
|
||||
_hasObject = true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_hasObject = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x1C, Pack = 1)]
|
||||
struct BufferInfo
|
||||
{
|
||||
public ulong FrameNumber;
|
||||
public TimeSpanType QueueTime;
|
||||
public TimeSpanType PresentationTime;
|
||||
public BufferState State;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class BufferItem : ICloneable
|
||||
{
|
||||
public AndroidStrongPointer<GraphicBuffer> GraphicBuffer;
|
||||
public AndroidFence Fence;
|
||||
public Rect Crop;
|
||||
public NativeWindowTransform Transform;
|
||||
public NativeWindowScalingMode ScalingMode;
|
||||
public long Timestamp;
|
||||
public bool IsAutoTimestamp;
|
||||
public int SwapInterval;
|
||||
public ulong FrameNumber;
|
||||
public int Slot;
|
||||
public bool IsDroppable;
|
||||
public bool AcquireCalled;
|
||||
public bool TransformToDisplayInverse;
|
||||
|
||||
public BufferItem()
|
||||
{
|
||||
GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>();
|
||||
Transform = NativeWindowTransform.None;
|
||||
ScalingMode = NativeWindowScalingMode.Freeze;
|
||||
Timestamp = 0;
|
||||
IsAutoTimestamp = false;
|
||||
FrameNumber = 0;
|
||||
Slot = BufferSlotArray.InvalidBufferSlot;
|
||||
IsDroppable = false;
|
||||
AcquireCalled = false;
|
||||
TransformToDisplayInverse = false;
|
||||
SwapInterval = 1;
|
||||
Fence = AndroidFence.NoFence;
|
||||
|
||||
Crop = new Rect();
|
||||
Crop.MakeInvalid();
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
BufferItem item = new BufferItem();
|
||||
|
||||
item.Transform = Transform;
|
||||
item.ScalingMode = ScalingMode;
|
||||
item.IsAutoTimestamp = IsAutoTimestamp;
|
||||
item.FrameNumber = FrameNumber;
|
||||
item.Slot = Slot;
|
||||
item.IsDroppable = IsDroppable;
|
||||
item.AcquireCalled = AcquireCalled;
|
||||
item.TransformToDisplayInverse = TransformToDisplayInverse;
|
||||
item.SwapInterval = SwapInterval;
|
||||
item.Fence = Fence;
|
||||
item.Crop = Crop;
|
||||
|
||||
item.GraphicBuffer.Set(GraphicBuffer);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
internal enum BufferState
|
||||
{
|
||||
Free = 0,
|
||||
Dequeued = 1,
|
||||
Queued = 2,
|
||||
Acquired = 3
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum ColorBytePerPixel
|
||||
{
|
||||
Bpp1 = 1,
|
||||
Bpp2 = 2,
|
||||
Bpp4 = 4,
|
||||
Bpp8 = 8,
|
||||
Bpp16 = 16,
|
||||
Bpp24 = 24,
|
||||
Bpp32 = 32,
|
||||
Bpp48 = 48,
|
||||
Bpp64 = 64,
|
||||
Bpp96 = 96,
|
||||
Bpp128 = 128
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum ColorComponent : uint
|
||||
{
|
||||
X1 = (0x01 << ColorShift.Component) | ColorBytePerPixel.Bpp1,
|
||||
X2 = (0x02 << ColorShift.Component) | ColorBytePerPixel.Bpp2,
|
||||
X4 = (0x03 << ColorShift.Component) | ColorBytePerPixel.Bpp4,
|
||||
X8 = (0x04 << ColorShift.Component) | ColorBytePerPixel.Bpp8,
|
||||
Y4X4 = (0x05 << ColorShift.Component) | ColorBytePerPixel.Bpp8,
|
||||
X3Y3Z2 = (0x06 << ColorShift.Component) | ColorBytePerPixel.Bpp8,
|
||||
X8Y8 = (0x07 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X8Y8X8Z8 = (0x08 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
Y8X8Z8X8 = (0x09 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X16 = (0x0A << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
Y2X14 = (0x0B << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
Y4X12 = (0x0C << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
Y6X10 = (0x0D << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
Y8X8 = (0x0E << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X10 = (0x0F << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X12 = (0x10 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
Z5Y5X6 = (0x11 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X5Y6Z5 = (0x12 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X6Y5Z5 = (0x13 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X1Y5Z5W5 = (0x14 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X4Y4Z4W4 = (0x15 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X5Y1Z5W5 = (0x16 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X5Y5Z1W5 = (0x17 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X5Y5Z5W1 = (0x18 << ColorShift.Component) | ColorBytePerPixel.Bpp16,
|
||||
X8Y8Z8 = (0x19 << ColorShift.Component) | ColorBytePerPixel.Bpp24,
|
||||
X24 = (0x1A << ColorShift.Component) | ColorBytePerPixel.Bpp24,
|
||||
X32 = (0x1C << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
X16Y16 = (0x1D << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
X11Y11Z10 = (0x1E << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
X2Y10Z10W10 = (0x20 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
X8Y8Z8W8 = (0x21 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
Y10X10 = (0x22 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
X10Y10Z10W2 = (0x23 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
Y12X12 = (0x24 << ColorShift.Component) | ColorBytePerPixel.Bpp32,
|
||||
X20Y20Z20 = (0x26 << ColorShift.Component) | ColorBytePerPixel.Bpp64,
|
||||
X16Y16Z16W16 = (0x27 << ColorShift.Component) | ColorBytePerPixel.Bpp64,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum ColorDataType
|
||||
{
|
||||
Integer = 0x0 << ColorShift.DataType,
|
||||
Float = 0x1 << ColorShift.DataType,
|
||||
Stencil = 0x2 << ColorShift.DataType
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum ColorFormat : ulong
|
||||
{
|
||||
NonColor8 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
NonColor16 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
|
||||
NonColor24 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X24 | ColorDataType.Integer,
|
||||
NonColor32 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X32 | ColorDataType.Integer,
|
||||
X4C4 = ColorSpace.NonColor | ColorSwizzle.Y000 | ColorComponent.Y4X4 | ColorDataType.Integer,
|
||||
A4L4 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y4X4 | ColorDataType.Integer,
|
||||
A8L8 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
Float_A16L16 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.X16Y16 | ColorDataType.Float,
|
||||
A1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
|
||||
A4B4G4R4 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
|
||||
A5B5G5R1 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
|
||||
A2B10G10R10 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
A8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
Float_A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
|
||||
A1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
|
||||
A4R4G4B4 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
|
||||
A5R1G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X5Y1Z5W5 | ColorDataType.Integer,
|
||||
A2R10G10B10 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
A8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A1 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X1 | ColorDataType.Integer,
|
||||
A2 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X2 | ColorDataType.Integer,
|
||||
A4 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X4 | ColorDataType.Integer,
|
||||
A8 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X8 | ColorDataType.Integer,
|
||||
A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Integer,
|
||||
A32 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X32 | ColorDataType.Integer,
|
||||
Float_A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Float,
|
||||
L4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y4X4 | ColorDataType.Integer,
|
||||
L8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
B4G4R4A4 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
|
||||
B5G5R1A5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z1W5 | ColorDataType.Integer,
|
||||
B5G5R5A1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
|
||||
B8G8R8A8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
B10G10R10A2 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
R1G5B5A5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
|
||||
R4G4B4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer,
|
||||
R5G5B5A1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
|
||||
R8G8B8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
R10G10B10A2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
L1 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X1 | ColorDataType.Integer,
|
||||
L2 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X2 | ColorDataType.Integer,
|
||||
L4 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X4 | ColorDataType.Integer,
|
||||
L8 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Integer,
|
||||
L32 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X32 | ColorDataType.Integer,
|
||||
Float_L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Float,
|
||||
B5G6R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer,
|
||||
B6G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X6Y5Z5 | ColorDataType.Integer,
|
||||
B5G5R5X1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
|
||||
B8_G8_R8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer,
|
||||
B8G8R8X8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
Float_B10G11R11 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X11Y11Z10 | ColorDataType.Float,
|
||||
X1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
|
||||
X8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
Float_X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
|
||||
R3G3B2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X3Y3Z2 | ColorDataType.Integer,
|
||||
R5G5B6 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.Z5Y5X6 | ColorDataType.Integer,
|
||||
R5G6B5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer,
|
||||
R5G5B5X1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer,
|
||||
R8_G8_B8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer,
|
||||
R8G8B8X8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
X1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer,
|
||||
X8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
RG8 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Integer,
|
||||
Float_R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Float,
|
||||
R8 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Integer,
|
||||
Float_R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Float,
|
||||
A2B10G10R10_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
A8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
A2R10G10B10_sRGB = ColorSpace.SRGB | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
B10G10R10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
R10G10B10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
X8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
X16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
A2B10G10R10_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
A8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
A2R10G10B10_709 = ColorSpace.RGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
B10G10R10A2_709 = ColorSpace.RGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
R10G10B10A2_709 = ColorSpace.RGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
X8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
X16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
A2B10G10R10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
A8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
A2R10G10B10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
B10G10R10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
R10G10B10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
X8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
X16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
Float_A16B16G16R16_scRGB_Linear = ColorSpace.LinearScRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
|
||||
A2B10G10R10_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
A8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
A2R10G10B10_2020 = ColorSpace.RGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
B10G10R10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
R10G10B10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
X8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
X16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
A2B10G10R10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
A8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
Float_A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
|
||||
A2R10G10B10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer,
|
||||
B10G10R10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
R10G10B10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer,
|
||||
X8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
X16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
Float_A16B16G16R16_2020_PQ = ColorSpace.RGB2020_PQ | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float,
|
||||
A4I4 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y4X4 | ColorDataType.Integer,
|
||||
A8I8 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
I4A4 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y4X4 | ColorDataType.Integer,
|
||||
I8A8 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
I1 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X1 | ColorDataType.Integer,
|
||||
I2 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X2 | ColorDataType.Integer,
|
||||
I4 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X4 | ColorDataType.Integer,
|
||||
I8 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
A8Y8U8V8 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
A16Y16U16V16 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer,
|
||||
Y8U8V8A8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer,
|
||||
V8_U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
V8U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
V10U10 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
V12U12 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
V8 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
V10 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
V12 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
U8V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
U10V10 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
U12V12 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
U8 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
U10 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
U12 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
Y8 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Y10 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
Y12 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
YVYU = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer,
|
||||
VYUY = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer,
|
||||
UYVY = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer,
|
||||
YUYV = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer,
|
||||
Y8_U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer,
|
||||
V8_U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
V8U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
U8_V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
U8V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Y8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
V8_U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
V8U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
U8_V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
U8V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Y8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
V8_U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
V8U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
V10U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
V12U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
U8_V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
U8V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
U10V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
U12V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
Y8_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Y10_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
Y12_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
V8_U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
V8U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
V10U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
V12U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
U8_V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer,
|
||||
U8V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer,
|
||||
U10V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
U12V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
Y8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Y10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
Y12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
V10U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
V12U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
U10V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer,
|
||||
U12V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer,
|
||||
U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
Y10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer,
|
||||
Y12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer,
|
||||
Bayer8RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Bayer16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
|
||||
BayerS16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
|
||||
X2Bayer14RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
|
||||
X4Bayer12RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
|
||||
X6Bayer10RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
|
||||
Bayer8BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Bayer16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
|
||||
BayerS16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
|
||||
X2Bayer14BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
|
||||
X4Bayer12BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
|
||||
X6Bayer10BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
|
||||
Bayer8GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Bayer16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
|
||||
BayerS16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
|
||||
X2Bayer14GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
|
||||
X4Bayer12GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
|
||||
X6Bayer10GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
|
||||
Bayer8GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer,
|
||||
Bayer16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer,
|
||||
BayerS16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil,
|
||||
X2Bayer14GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer,
|
||||
X4Bayer12GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer,
|
||||
X6Bayer10GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer,
|
||||
XYZ = ColorSpace.XYZ | ColorSwizzle.XYZ1 | ColorComponent.X20Y20Z20 | ColorDataType.Float,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class ColorShift
|
||||
{
|
||||
public const int Swizzle = 16;
|
||||
public const int DataType = 14;
|
||||
public const int Space = 32;
|
||||
public const int Component = 8;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum ColorSpace : ulong
|
||||
{
|
||||
NonColor = 0x0L << ColorShift.Space,
|
||||
LinearRGBA = 0x1L << ColorShift.Space,
|
||||
SRGB = 0x2L << ColorShift.Space,
|
||||
|
||||
RGB709 = 0x3L << ColorShift.Space,
|
||||
LinearRGB709 = 0x4L << ColorShift.Space,
|
||||
|
||||
LinearScRGB = 0x5L << ColorShift.Space,
|
||||
|
||||
RGB2020 = 0x6L << ColorShift.Space,
|
||||
LinearRGB2020 = 0x7L << ColorShift.Space,
|
||||
RGB2020_PQ = 0x8L << ColorShift.Space,
|
||||
|
||||
ColorIndex = 0x9L << ColorShift.Space,
|
||||
|
||||
YCbCr601 = 0xAL << ColorShift.Space,
|
||||
YCbCr601_RR = 0xBL << ColorShift.Space,
|
||||
YCbCr601_ER = 0xCL << ColorShift.Space,
|
||||
YCbCr709 = 0xDL << ColorShift.Space,
|
||||
YCbCr709_ER = 0xEL << ColorShift.Space,
|
||||
|
||||
BayerRGGB = 0x10L << ColorShift.Space,
|
||||
BayerBGGR = 0x11L << ColorShift.Space,
|
||||
BayerGRBG = 0x12L << ColorShift.Space,
|
||||
BayerGBRG = 0x13L << ColorShift.Space,
|
||||
|
||||
XYZ = 0x14L << ColorShift.Space,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
enum ColorSwizzle
|
||||
{
|
||||
XYZW = 0x688 << ColorShift.Swizzle,
|
||||
ZYXW = 0x60a << ColorShift.Swizzle,
|
||||
WZYX = 0x053 << ColorShift.Swizzle,
|
||||
YZWX = 0x0d1 << ColorShift.Swizzle,
|
||||
XYZ1 = 0xa88 << ColorShift.Swizzle,
|
||||
YZW1 = 0xad1 << ColorShift.Swizzle,
|
||||
XXX1 = 0xa00 << ColorShift.Swizzle,
|
||||
XZY1 = 0xa50 << ColorShift.Swizzle,
|
||||
ZYX1 = 0xa0a << ColorShift.Swizzle,
|
||||
WZY1 = 0xa53 << ColorShift.Swizzle,
|
||||
X000 = 0x920 << ColorShift.Swizzle,
|
||||
Y000 = 0x921 << ColorShift.Swizzle,
|
||||
XY01 = 0xb08 << ColorShift.Swizzle,
|
||||
X001 = 0xb20 << ColorShift.Swizzle,
|
||||
X00X = 0x121 << ColorShift.Swizzle,
|
||||
X00Y = 0x320 << ColorShift.Swizzle,
|
||||
_0YX0 = 0x80c << ColorShift.Swizzle,
|
||||
_0ZY0 = 0x814 << ColorShift.Swizzle,
|
||||
_0XZ0 = 0x884 << ColorShift.Swizzle,
|
||||
_0X00 = 0x904 << ColorShift.Swizzle,
|
||||
_00X0 = 0x824 << ColorShift.Swizzle,
|
||||
_000X = 0x124 << ColorShift.Swizzle,
|
||||
_0XY0 = 0x844 << ColorShift.Swizzle,
|
||||
XXXY = 0x200 << ColorShift.Swizzle,
|
||||
YYYX = 0x049 << ColorShift.Swizzle
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct GraphicBuffer : IFlattenable
|
||||
{
|
||||
public GraphicBufferHeader Header;
|
||||
public NvGraphicBuffer Buffer;
|
||||
|
||||
public int Width => Header.Width;
|
||||
public int Height => Header.Height;
|
||||
public PixelFormat Format => Header.Format;
|
||||
public int Usage => Header.Usage;
|
||||
|
||||
public Rect ToRect()
|
||||
{
|
||||
return new Rect(Width, Height);
|
||||
}
|
||||
|
||||
public void Flatten(Parcel parcel)
|
||||
{
|
||||
parcel.WriteUnmanagedType(ref Header);
|
||||
parcel.WriteUnmanagedType(ref Buffer);
|
||||
}
|
||||
|
||||
public void Unflatten(Parcel parcel)
|
||||
{
|
||||
Header = parcel.ReadUnmanagedType<GraphicBufferHeader>();
|
||||
|
||||
int expectedSize = Unsafe.SizeOf<NvGraphicBuffer>() / 4;
|
||||
|
||||
if (Header.IntsCount != expectedSize)
|
||||
{
|
||||
throw new NotImplementedException($"Unexpected Graphic Buffer ints count (expected 0x{expectedSize:x}, found 0x{Header.IntsCount:x})");
|
||||
}
|
||||
|
||||
Buffer = parcel.ReadUnmanagedType<NvGraphicBuffer>();
|
||||
}
|
||||
|
||||
public void IncrementNvMapHandleRefCount(ulong pid)
|
||||
{
|
||||
NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.NvMapId);
|
||||
|
||||
for (int i = 0; i < Buffer.Surfaces.Length; i++)
|
||||
{
|
||||
NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void DecrementNvMapHandleRefCount(ulong pid)
|
||||
{
|
||||
NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.NvMapId);
|
||||
|
||||
for (int i = 0; i < Buffer.Surfaces.Length; i++)
|
||||
{
|
||||
NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetFlattenedSize()
|
||||
{
|
||||
return (uint)Unsafe.SizeOf<GraphicBuffer>();
|
||||
}
|
||||
|
||||
public uint GetFdCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 1)]
|
||||
struct GraphicBufferHeader
|
||||
{
|
||||
public int Magic;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public int Stride;
|
||||
public PixelFormat Format;
|
||||
public int Usage;
|
||||
|
||||
public int Pid;
|
||||
public int RefCount;
|
||||
|
||||
public int FdsCount;
|
||||
public int IntsCount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x144, Pack = 1)]
|
||||
struct NvGraphicBuffer
|
||||
{
|
||||
[FieldOffset(0x4)]
|
||||
public int NvMapId;
|
||||
|
||||
[FieldOffset(0xC)]
|
||||
public int Magic;
|
||||
|
||||
[FieldOffset(0x10)]
|
||||
public int Pid;
|
||||
|
||||
[FieldOffset(0x14)]
|
||||
public int Type;
|
||||
|
||||
[FieldOffset(0x18)]
|
||||
public int Usage;
|
||||
|
||||
[FieldOffset(0x1C)]
|
||||
public int PixelFormat;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public int ExternalPixelFormat;
|
||||
|
||||
[FieldOffset(0x24)]
|
||||
public int Stride;
|
||||
|
||||
[FieldOffset(0x28)]
|
||||
public int FrameBufferSize;
|
||||
|
||||
[FieldOffset(0x2C)]
|
||||
public int PlanesCount;
|
||||
|
||||
[FieldOffset(0x34)]
|
||||
public NvGraphicBufferSurfaceArray Surfaces;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x58)]
|
||||
struct NvGraphicBufferSurface
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public uint Width;
|
||||
|
||||
[FieldOffset(0x4)]
|
||||
public uint Height;
|
||||
|
||||
[FieldOffset(0x8)]
|
||||
public ColorFormat ColorFormat;
|
||||
|
||||
[FieldOffset(0x10)]
|
||||
public int Layout;
|
||||
|
||||
[FieldOffset(0x14)]
|
||||
public int Pitch;
|
||||
|
||||
[FieldOffset(0x18)]
|
||||
public int NvMapHandle;
|
||||
|
||||
[FieldOffset(0x1C)]
|
||||
public int Offset;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public int Kind;
|
||||
|
||||
[FieldOffset(0x24)]
|
||||
public int BlockHeightLog2;
|
||||
|
||||
[FieldOffset(0x28)]
|
||||
public int ScanFormat;
|
||||
|
||||
[FieldOffset(0x30)]
|
||||
public long Flags;
|
||||
|
||||
[FieldOffset(0x38)]
|
||||
public long Size;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
struct NvGraphicBufferSurfaceArray
|
||||
{
|
||||
[FieldOffset(0x0)]
|
||||
private NvGraphicBufferSurface Surface0;
|
||||
|
||||
[FieldOffset(0x58)]
|
||||
private NvGraphicBufferSurface Surface1;
|
||||
|
||||
[FieldOffset(0xb0)]
|
||||
private NvGraphicBufferSurface Surface2;
|
||||
|
||||
public NvGraphicBufferSurface this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
return Surface0;
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
return Surface1;
|
||||
}
|
||||
else if (index == 2)
|
||||
{
|
||||
return Surface2;
|
||||
}
|
||||
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public int Length => 3;
|
||||
}
|
||||
}
|
71
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs
Normal file
71
src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
struct Rect : IEquatable<Rect>
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public int Width => Right - Left;
|
||||
public int Height => Bottom - Top;
|
||||
|
||||
public Rect(int width, int height)
|
||||
{
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Right = width;
|
||||
Bottom = height;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return Width <= 0 || Height <= 0;
|
||||
}
|
||||
|
||||
public bool Intersect(Rect other, out Rect result)
|
||||
{
|
||||
result = new Rect
|
||||
{
|
||||
Left = Math.Max(Left, other.Left),
|
||||
Top = Math.Max(Top, other.Top),
|
||||
Right = Math.Min(Right, other.Right),
|
||||
Bottom = Math.Min(Bottom, other.Bottom)
|
||||
};
|
||||
|
||||
return !result.IsEmpty();
|
||||
}
|
||||
|
||||
public void MakeInvalid()
|
||||
{
|
||||
Right = -1;
|
||||
Bottom = -1;
|
||||
}
|
||||
|
||||
public static bool operator ==(Rect x, Rect y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public static bool operator !=(Rect x, Rect y)
|
||||
{
|
||||
return !x.Equals(y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Rect rect && Equals(rect);
|
||||
}
|
||||
|
||||
public bool Equals(Rect cmpObj)
|
||||
{
|
||||
return Left == cmpObj.Left && Top == cmpObj.Top && Right == cmpObj.Right && Bottom == cmpObj.Bottom;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue