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