| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- using Ryujinx.Common.Configuration;
- using Ryujinx.Common.Logging;
- using Ryujinx.Graphics.GAL;
- using Ryujinx.Graphics.Gpu;
- using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using System.Threading;
- namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
- {
- using ResultCode = Ryujinx.HLE.HOS.Services.Vi.ResultCode;
- class SurfaceFlinger : IConsumerListener, IDisposable
- {
- private const int TargetFps = 60;
- private Switch _device;
- private Dictionary<long, Layer> _layers;
- private bool _isRunning;
- private Thread _composerThread;
- private Stopwatch _chrono;
- private ManualResetEvent _event = new ManualResetEvent(false);
- private AutoResetEvent _nextFrameEvent = new AutoResetEvent(true);
- private long _ticks;
- private long _ticksPerFrame;
- private long _spinTicks;
- private long _1msTicks;
- private int _swapInterval;
- private readonly object Lock = new object();
- public long RenderLayerId { get; private set; }
- private class Layer
- {
- public int ProducerBinderId;
- public IGraphicBufferProducer Producer;
- public BufferItemConsumer Consumer;
- public BufferQueueCore Core;
- public ulong Owner;
- public LayerState State;
- }
- private class TextureCallbackInformation
- {
- public Layer Layer;
- public BufferItem Item;
- }
- public SurfaceFlinger(Switch device)
- {
- _device = device;
- _layers = new Dictionary<long, Layer>();
- RenderLayerId = 0;
- _composerThread = new Thread(HandleComposition)
- {
- Name = "SurfaceFlinger.Composer"
- };
- _chrono = new Stopwatch();
- _chrono.Start();
- _ticks = 0;
- _spinTicks = Stopwatch.Frequency / 500;
- _1msTicks = Stopwatch.Frequency / 1000;
- UpdateSwapInterval(1);
- _composerThread.Start();
- }
- private void UpdateSwapInterval(int swapInterval)
- {
- _swapInterval = swapInterval;
- // If the swap interval is 0, Game VSync is disabled.
- if (_swapInterval == 0)
- {
- _nextFrameEvent.Set();
- _ticksPerFrame = 1;
- }
- else
- {
- _ticksPerFrame = Stopwatch.Frequency / (TargetFps / _swapInterval);
- }
- }
- public IGraphicBufferProducer CreateLayer(out long layerId, ulong pid, LayerState initialState = LayerState.ManagedClosed)
- {
- layerId = 1;
- lock (Lock)
- {
- foreach (KeyValuePair<long, Layer> pair in _layers)
- {
- if (pair.Key >= layerId)
- {
- layerId = pair.Key + 1;
- }
- }
- }
- CreateLayerFromId(pid, layerId, initialState);
- return GetProducerByLayerId(layerId);
- }
- private void CreateLayerFromId(ulong pid, long layerId, LayerState initialState)
- {
- lock (Lock)
- {
- Logger.Info?.Print(LogClass.SurfaceFlinger, $"Creating layer {layerId}");
- BufferQueueCore core = BufferQueue.CreateBufferQueue(_device, pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer);
- core.BufferQueued += () =>
- {
- _nextFrameEvent.Set();
- };
- _layers.Add(layerId, new Layer
- {
- ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer),
- Producer = producer,
- Consumer = new BufferItemConsumer(_device, consumer, 0, -1, false, this),
- Core = core,
- Owner = pid,
- State = initialState
- });
- }
- }
- public ResultCode OpenLayer(ulong pid, long layerId, out IBinder producer)
- {
- Layer layer = GetLayerByIdLocked(layerId);
- if (layer == null || layer.State != LayerState.ManagedClosed)
- {
- producer = null;
- return ResultCode.InvalidArguments;
- }
- layer.State = LayerState.ManagedOpened;
- producer = layer.Producer;
- return ResultCode.Success;
- }
- public ResultCode CloseLayer(long layerId)
- {
- lock (Lock)
- {
- Layer layer = GetLayerByIdLocked(layerId);
- if (layer == null)
- {
- Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to close layer {layerId}");
- return ResultCode.InvalidValue;
- }
- CloseLayer(layerId, layer);
- return ResultCode.Success;
- }
- }
- public ResultCode DestroyManagedLayer(long layerId)
- {
- lock (Lock)
- {
- Layer layer = GetLayerByIdLocked(layerId);
- if (layer == null)
- {
- Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (not found)");
- return ResultCode.InvalidValue;
- }
- if (layer.State != LayerState.ManagedClosed && layer.State != LayerState.ManagedOpened)
- {
- Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (permission denied)");
- return ResultCode.PermissionDenied;
- }
- HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
- if (_layers.Remove(layerId) && layer.State == LayerState.ManagedOpened)
- {
- CloseLayer(layerId, layer);
- }
- return ResultCode.Success;
- }
- }
- public ResultCode DestroyStrayLayer(long layerId)
- {
- lock (Lock)
- {
- Layer layer = GetLayerByIdLocked(layerId);
- if (layer == null)
- {
- Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (not found)");
- return ResultCode.InvalidValue;
- }
- if (layer.State != LayerState.Stray)
- {
- Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (permission denied)");
- return ResultCode.PermissionDenied;
- }
- HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
- if (_layers.Remove(layerId))
- {
- CloseLayer(layerId, layer);
- }
- return ResultCode.Success;
- }
- }
- private void CloseLayer(long layerId, Layer layer)
- {
- // If the layer was removed and the current in use, we need to change the current layer in use.
- if (RenderLayerId == layerId)
- {
- // If no layer is availaible, reset to default value.
- if (_layers.Count == 0)
- {
- SetRenderLayer(0);
- }
- else
- {
- SetRenderLayer(_layers.Last().Key);
- }
- }
- if (layer.State == LayerState.ManagedOpened)
- {
- layer.State = LayerState.ManagedClosed;
- }
- }
- public void SetRenderLayer(long layerId)
- {
- lock (Lock)
- {
- RenderLayerId = layerId;
- }
- }
- private Layer GetLayerByIdLocked(long layerId)
- {
- foreach (KeyValuePair<long, Layer> pair in _layers)
- {
- if (pair.Key == layerId)
- {
- return pair.Value;
- }
- }
- return null;
- }
- public IGraphicBufferProducer GetProducerByLayerId(long layerId)
- {
- lock (Lock)
- {
- Layer layer = GetLayerByIdLocked(layerId);
- if (layer != null)
- {
- return layer.Producer;
- }
- }
- return null;
- }
- private void HandleComposition()
- {
- _isRunning = true;
- long lastTicks = _chrono.ElapsedTicks;
- while (_isRunning)
- {
- long ticks = _chrono.ElapsedTicks;
- if (_swapInterval == 0)
- {
- Compose();
- _device.System?.SignalVsync();
- _nextFrameEvent.WaitOne(17);
- lastTicks = ticks;
- }
- else
- {
- _ticks += ticks - lastTicks;
- lastTicks = ticks;
- if (_ticks >= _ticksPerFrame)
- {
- Compose();
- _device.System?.SignalVsync();
- // Apply a maximum bound of 3 frames to the tick remainder, in case some event causes Ryujinx to pause for a long time or messes with the timer.
- _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame * 3);
- }
- // Sleep if possible. If the time til the next frame is too low, spin wait instead.
- long diff = _ticksPerFrame - (_ticks + _chrono.ElapsedTicks - ticks);
- if (diff > 0)
- {
- if (diff < _spinTicks)
- {
- do
- {
- // SpinWait is a little more HT/SMT friendly than aggressively updating/checking ticks.
- // The value of 5 still gives us quite a bit of precision (~0.0003ms variance at worst) while waiting a reasonable amount of time.
- Thread.SpinWait(5);
- ticks = _chrono.ElapsedTicks;
- _ticks += ticks - lastTicks;
- lastTicks = ticks;
- } while (_ticks < _ticksPerFrame);
- }
- else
- {
- _event.WaitOne((int)(diff / _1msTicks));
- }
- }
- }
- }
- }
- public void Compose()
- {
- lock (Lock)
- {
- // TODO: support multilayers (& multidisplay ?)
- if (RenderLayerId == 0)
- {
- return;
- }
- Layer layer = GetLayerByIdLocked(RenderLayerId);
- Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
- if (acquireStatus == Status.Success)
- {
- // If device vsync is disabled, reflect the change.
- if (!_device.EnableDeviceVsync)
- {
- if (_swapInterval != 0)
- {
- UpdateSwapInterval(0);
- }
- }
- else if (item.SwapInterval != _swapInterval)
- {
- UpdateSwapInterval(item.SwapInterval);
- }
- PostFrameBuffer(layer, item);
- }
- else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation)
- {
- throw new InvalidOperationException();
- }
- }
- }
- private void PostFrameBuffer(Layer layer, BufferItem item)
- {
- int frameBufferWidth = item.GraphicBuffer.Object.Width;
- int frameBufferHeight = item.GraphicBuffer.Object.Height;
- int nvMapHandle = item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle;
- if (nvMapHandle == 0)
- {
- nvMapHandle = item.GraphicBuffer.Object.Buffer.NvMapId;
- }
- ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
- NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle);
- ulong frameBufferAddress = map.Address + bufferOffset;
- Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
- int bytesPerPixel =
- format == Format.B5G6R5Unorm ||
- format == Format.R4G4B4A4Unorm ? 2 : 4;
- int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2;
- // Note: Rotation is being ignored.
- Rect cropRect = item.Crop;
- bool flipX = item.Transform.HasFlag(NativeWindowTransform.FlipX);
- bool flipY = item.Transform.HasFlag(NativeWindowTransform.FlipY);
- AspectRatio aspectRatio = _device.Configuration.AspectRatio;
- bool isStretched = aspectRatio == AspectRatio.Stretched;
- ImageCrop crop = new ImageCrop(
- cropRect.Left,
- cropRect.Right,
- cropRect.Top,
- cropRect.Bottom,
- flipX,
- flipY,
- isStretched,
- aspectRatio.ToFloatX(),
- aspectRatio.ToFloatY());
- TextureCallbackInformation textureCallbackInformation = new TextureCallbackInformation
- {
- Layer = layer,
- Item = item
- };
- _device.Gpu.Window.EnqueueFrameThreadSafe(
- layer.Owner,
- frameBufferAddress,
- frameBufferWidth,
- frameBufferHeight,
- 0,
- false,
- gobBlocksInY,
- format,
- bytesPerPixel,
- crop,
- AcquireBuffer,
- ReleaseBuffer,
- textureCallbackInformation);
- if (item.Fence.FenceCount == 0)
- {
- _device.Gpu.Window.SignalFrameReady();
- _device.Gpu.GPFifo.Interrupt();
- }
- else
- {
- item.Fence.RegisterCallback(_device.Gpu, (x) =>
- {
- _device.Gpu.Window.SignalFrameReady();
- _device.Gpu.GPFifo.Interrupt();
- });
- }
- }
- private void ReleaseBuffer(object obj)
- {
- ReleaseBuffer((TextureCallbackInformation)obj);
- }
- private void ReleaseBuffer(TextureCallbackInformation information)
- {
- AndroidFence fence = AndroidFence.NoFence;
- information.Layer.Consumer.ReleaseBuffer(information.Item, ref fence);
- }
- private void AcquireBuffer(GpuContext ignored, object obj)
- {
- AcquireBuffer((TextureCallbackInformation)obj);
- }
- private void AcquireBuffer(TextureCallbackInformation information)
- {
- information.Item.Fence.WaitForever(_device.Gpu);
- }
- public static Format ConvertColorFormat(ColorFormat colorFormat)
- {
- return colorFormat switch
- {
- ColorFormat.A8B8G8R8 => Format.R8G8B8A8Unorm,
- ColorFormat.X8B8G8R8 => Format.R8G8B8A8Unorm,
- ColorFormat.R5G6B5 => Format.B5G6R5Unorm,
- ColorFormat.A8R8G8B8 => Format.B8G8R8A8Unorm,
- ColorFormat.A4B4G4R4 => Format.R4G4B4A4Unorm,
- _ => throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"),
- };
- }
- public void Dispose()
- {
- _isRunning = false;
- foreach (Layer layer in _layers.Values)
- {
- layer.Core.PrepareForExit();
- }
- }
- public void OnFrameAvailable(ref BufferItem item)
- {
- _device.Statistics.RecordGameFrameTime();
- }
- public void OnFrameReplaced(ref BufferItem item)
- {
- _device.Statistics.RecordGameFrameTime();
- }
- public void OnBuffersReleased() {}
- }
- }
|