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