Implement GPU syncpoints (#980)

* Implement GPU syncpoints

This adds support for GPU syncpoints on the GPU backend & nvservices.

Everything that was implemented here is based on my researches,
hardware testing of the GM20B and reversing of nvservices (8.1.0).

Thanks to @fincs for the informations about some behaviours of the pusher
and for the initial informations about syncpoints.

* syncpoint: address gdkchan's comments

* Add some missing logic to handle SubmitGpfifo correctly

* Handle the NV event API correctly

* evnt => hostEvent

* Finish addressing gdkchan's comments

* nvservices: write the output buffer even when an error is returned

* dma pusher: Implemnet prefetch barrier

lso fix when the commands should be prefetch.

* Partially fix prefetch barrier

* Add a missing syncpoint check in QueryEvent of NvHostSyncPt

* Address Ac_K's comments and fix GetSyncpoint for ChannelResourcePolicy == Channel

* fix SyncptWait & SyncptWaitEx cmds logic

* Address ripinperi's comments

* Address gdkchan's comments

* Move user event management to the control channel

* Fix mm implementation, nvdec works again

* Address ripinperi's comments

* Address gdkchan's comments

* Implement nvhost-ctrl close accurately + make nvservices dispose channels when stopping the emulator

* Fix typo in MultiMediaOperationType
This commit is contained in:
Thog 2020-04-19 03:25:57 +02:00 committed by GitHub
parent 4960ab85f8
commit 644de99e86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1576 additions and 386 deletions

View File

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Gpu
Engine3D = 0xb197, Engine3D = 0xb197,
EngineCompute = 0xb1c0, EngineCompute = 0xb1c0,
EngineInline2Memory = 0xa140, EngineInline2Memory = 0xa140,
EngineDma = 0xb0b5 EngineDma = 0xb0b5,
EngineGpfifo = 0xb06f
} }
} }

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
namespace Ryujinx.Graphics.Gpu namespace Ryujinx.Graphics.Gpu
@ -8,10 +10,61 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
public class DmaPusher public class DmaPusher
{ {
private ConcurrentQueue<ulong> _ibBuffer; private ConcurrentQueue<CommandBuffer> _commandBufferQueue;
private ulong _dmaPut; private enum CommandBufferType
private ulong _dmaGet; {
Prefetch,
NoPrefetch,
}
private struct CommandBuffer
{
/// <summary>
/// The type of the command buffer.
/// </summary>
public CommandBufferType Type;
/// <summary>
/// Fetched data.
/// </summary>
public int[] Words;
/// <summary>
/// The GPFIFO entry address. (used in NoPrefetch mode)
/// </summary>
public ulong EntryAddress;
/// <summary>
/// The count of entries inside this GPFIFO entry.
/// </summary>
public uint EntryCount;
/// <summary>
/// Fetch the command buffer.
/// </summary>
public void Fetch(GpuContext context)
{
if (Words == null)
{
Words = MemoryMarshal.Cast<byte, int>(context.MemoryAccessor.GetSpan(EntryAddress, EntryCount * 4)).ToArray();
}
}
/// <summary>
/// Read inside the command buffer.
/// </summary>
/// <param name="context">The GPU context</param>
/// <param name="index">The index inside the command buffer</param>
/// <returns>The value read</returns>
public int ReadAt(GpuContext context, int index)
{
return Words[index];
}
}
private CommandBuffer _currentCommandBuffer;
private int _wordsPosition;
/// <summary> /// <summary>
/// Internal GPFIFO state. /// Internal GPFIFO state.
@ -32,9 +85,6 @@ namespace Ryujinx.Graphics.Gpu
private bool _sliActive; private bool _sliActive;
private bool _ibEnable; private bool _ibEnable;
private bool _nonMain;
private ulong _dmaMGet;
private GpuContext _context; private GpuContext _context;
@ -48,24 +98,91 @@ namespace Ryujinx.Graphics.Gpu
{ {
_context = context; _context = context;
_ibBuffer = new ConcurrentQueue<ulong>();
_ibEnable = true; _ibEnable = true;
_commandBufferQueue = new ConcurrentQueue<CommandBuffer>();
_event = new AutoResetEvent(false); _event = new AutoResetEvent(false);
} }
/// <summary> /// <summary>
/// Pushes a GPFIFO entry. /// Signal the pusher that there are new entries to process.
/// </summary> /// </summary>
/// <param name="entry">GPFIFO entry</param> public void SignalNewEntries()
public void Push(ulong entry)
{ {
_ibBuffer.Enqueue(entry);
_event.Set(); _event.Set();
} }
/// <summary>
/// Push a GPFIFO entry in the form of a prefetched command buffer.
/// It is intended to be used by nvservices to handle special cases.
/// </summary>
/// <param name="commandBuffer">The command buffer containing the prefetched commands</param>
public void PushHostCommandBuffer(int[] commandBuffer)
{
_commandBufferQueue.Enqueue(new CommandBuffer
{
Type = CommandBufferType.Prefetch,
Words = commandBuffer,
EntryAddress = ulong.MaxValue,
EntryCount = (uint)commandBuffer.Length
});
}
/// <summary>
/// Create a CommandBuffer from a GPFIFO entry.
/// </summary>
/// <param name="entry">The GPFIFO entry</param>
/// <returns>A new CommandBuffer based on the GPFIFO entry</returns>
private CommandBuffer CreateCommandBuffer(ulong entry)
{
ulong length = (entry >> 42) & 0x1fffff;
ulong startAddress = entry & 0xfffffffffc;
bool noPrefetch = (entry & (1UL << 63)) != 0;
CommandBufferType type = CommandBufferType.Prefetch;
if (noPrefetch)
{
type = CommandBufferType.NoPrefetch;
}
return new CommandBuffer
{
Type = type,
Words = null,
EntryAddress = startAddress,
EntryCount = (uint)length
};
}
/// <summary>
/// Pushes GPFIFO entries.
/// </summary>
/// <param name="entries">GPFIFO entries</param>
public void PushEntries(ReadOnlySpan<ulong> entries)
{
bool beforeBarrier = true;
foreach (ulong entry in entries)
{
CommandBuffer commandBuffer = CreateCommandBuffer(entry);
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
{
commandBuffer.Fetch(_context);
}
if (commandBuffer.Type == CommandBufferType.NoPrefetch)
{
beforeBarrier = false;
}
_commandBufferQueue.Enqueue(commandBuffer);
}
}
/// <summary> /// <summary>
/// Waits until commands are pushed to the FIFO. /// Waits until commands are pushed to the FIFO.
/// </summary> /// </summary>
@ -89,16 +206,9 @@ namespace Ryujinx.Graphics.Gpu
/// <returns>True if the FIFO still has commands to be processed, false otherwise</returns> /// <returns>True if the FIFO still has commands to be processed, false otherwise</returns>
private bool Step() private bool Step()
{ {
if (_dmaGet != _dmaPut) if (_wordsPosition != _currentCommandBuffer.EntryCount)
{ {
int word = _context.MemoryAccessor.ReadInt32(_dmaGet); int word = _currentCommandBuffer.ReadAt(_context, _wordsPosition++);
_dmaGet += 4;
if (!_nonMain)
{
_dmaMGet = _dmaGet;
}
if (_state.LengthPending != 0) if (_state.LengthPending != 0)
{ {
@ -170,14 +280,12 @@ namespace Ryujinx.Graphics.Gpu
} }
} }
} }
else if (_ibEnable && _ibBuffer.TryDequeue(out ulong entry)) else if (_ibEnable && _commandBufferQueue.TryDequeue(out CommandBuffer entry))
{ {
ulong length = (entry >> 42) & 0x1fffff; _currentCommandBuffer = entry;
_wordsPosition = 0;
_dmaGet = entry & 0xfffffffffc; _currentCommandBuffer.Fetch(_context);
_dmaPut = _dmaGet + length * 4;
_nonMain = (entry & (1UL << 41)) != 0;
} }
else else
{ {

View File

@ -0,0 +1,77 @@
using Ryujinx.Graphics.Gpu.State;
using System;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
/// <summary>
/// Waits for the GPU to be idle.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="argument">Method call argument</param>
public void WaitForIdle(GpuState state, int argument)
{
PerformDeferredDraws();
_context.Renderer.Pipeline.Barrier();
}
/// <summary>
/// Send macro code/data to the MME.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="argument">Method call argument</param>
public void SendMacroCodeData(GpuState state, int argument)
{
int macroUploadAddress = state.Get<int>(MethodOffset.MacroUploadAddress);
_context.Fifo.SendMacroCodeData(macroUploadAddress++, argument);
state.Write((int)MethodOffset.MacroUploadAddress, macroUploadAddress);
}
/// <summary>
/// Bind a macro index to a position for the MME.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="argument">Method call argument</param>
public void BindMacro(GpuState state, int argument)
{
int macroBindingIndex = state.Get<int>(MethodOffset.MacroBindingIndex);
_context.Fifo.BindMacro(macroBindingIndex++, argument);
state.Write((int)MethodOffset.MacroBindingIndex, macroBindingIndex);
}
public void SetMmeShadowRamControl(GpuState state, int argument)
{
_context.Fifo.SetMmeShadowRamControl((ShadowRamControl)argument);
}
/// <summary>
/// Apply a fence operation on a syncpoint.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="argument">Method call argument</param>
public void FenceAction(GpuState state, int argument)
{
uint threshold = state.Get<uint>(MethodOffset.FenceValue);
FenceActionOperation operation = (FenceActionOperation)(argument & 1);
uint syncpointId = (uint)(argument >> 8) & 0xFF;
if (operation == FenceActionOperation.Acquire)
{
_context.Synchronization.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan);
}
else if (operation == FenceActionOperation.Increment)
{
_context.Synchronization.IncrementSyncpoint(syncpointId);
}
}
}
}

View File

@ -0,0 +1,19 @@
using Ryujinx.Graphics.Gpu.State;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
/// <summary>
/// Performs an incrementation on a syncpoint.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="argument">Method call argument</param>
public void IncrementSyncpoint(GpuState state, int argument)
{
uint syncpointId = (uint)(argument) & 0xFFFF;
_context.Synchronization.IncrementSyncpoint(syncpointId);
}
}
}

View File

@ -26,16 +26,16 @@ namespace Ryujinx.Graphics.Gpu.Engine
switch (mode) switch (mode)
{ {
case ReportMode.Semaphore: ReportSemaphore(state); break; case ReportMode.Release: ReleaseSemaphore(state); break;
case ReportMode.Counter: ReportCounter(state, type); break; case ReportMode.Counter: ReportCounter(state, type); break;
} }
} }
/// <summary> /// <summary>
/// Writes a GPU semaphore value to guest memory. /// Writes (or Releases) a GPU semaphore value to guest memory.
/// </summary> /// </summary>
/// <param name="state">Current GPU state</param> /// <param name="state">Current GPU state</param>
private void ReportSemaphore(GpuState state) private void ReleaseSemaphore(GpuState state)
{ {
var rs = state.Get<ReportState>(MethodOffset.ReportState); var rs = state.Get<ReportState>(MethodOffset.ReportState);

View File

@ -65,6 +65,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
state.RegisterCallback(MethodOffset.Dispatch, Dispatch); state.RegisterCallback(MethodOffset.Dispatch, Dispatch);
state.RegisterCallback(MethodOffset.SyncpointAction, IncrementSyncpoint);
state.RegisterCallback(MethodOffset.CopyBuffer, CopyBuffer); state.RegisterCallback(MethodOffset.CopyBuffer, CopyBuffer);
state.RegisterCallback(MethodOffset.CopyTexture, CopyTexture); state.RegisterCallback(MethodOffset.CopyTexture, CopyTexture);
@ -94,6 +96,19 @@ namespace Ryujinx.Graphics.Gpu.Engine
state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment); state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment);
} }
/// <summary>
/// Register callback for Fifo method calls that triggers an action on the GPFIFO.
/// </summary>
/// <param name="state">GPU state where the triggers will be registered</param>
public void RegisterCallbacksForFifo(GpuState state)
{
state.RegisterCallback(MethodOffset.FenceAction, FenceAction);
state.RegisterCallback(MethodOffset.WaitForIdle, WaitForIdle);
state.RegisterCallback(MethodOffset.SendMacroCodeData, SendMacroCodeData);
state.RegisterCallback(MethodOffset.BindMacro, BindMacro);
state.RegisterCallback(MethodOffset.SetMmeShadowRamControl, SetMmeShadowRamControl);
}
/// <summary> /// <summary>
/// Updates host state based on the current guest GPU state. /// Updates host state based on the current guest GPU state.
/// </summary> /// </summary>

View File

@ -1,6 +1,7 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine; using Ryujinx.Graphics.Gpu.Engine;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Synchronization;
using System; using System;
namespace Ryujinx.Graphics.Gpu namespace Ryujinx.Graphics.Gpu
@ -45,6 +46,11 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
public DmaPusher DmaPusher { get; } public DmaPusher DmaPusher { get; }
/// <summary>
/// GPU synchronization manager.
/// </summary>
public SynchronizationManager Synchronization { get; }
/// <summary> /// <summary>
/// Presentation window. /// Presentation window.
/// </summary> /// </summary>
@ -81,6 +87,8 @@ namespace Ryujinx.Graphics.Gpu
DmaPusher = new DmaPusher(this); DmaPusher = new DmaPusher(this);
Synchronization = new SynchronizationManager();
Window = new Window(this); Window = new Window(this);
_caps = new Lazy<Capabilities>(Renderer.GetCapabilities); _caps = new Lazy<Capabilities>(Renderer.GetCapabilities);

View File

@ -123,6 +123,8 @@ namespace Ryujinx.Graphics.Gpu
private SubChannel[] _subChannels; private SubChannel[] _subChannels;
private SubChannel _fifoChannel;
/// <summary> /// <summary>
/// Creates a new instance of the GPU commands FIFO. /// Creates a new instance of the GPU commands FIFO.
/// </summary> /// </summary>
@ -135,76 +137,68 @@ namespace Ryujinx.Graphics.Gpu
_mme = new int[MmeWords]; _mme = new int[MmeWords];
_fifoChannel = new SubChannel();
_context.Methods.RegisterCallbacksForFifo(_fifoChannel.State);
_subChannels = new SubChannel[8]; _subChannels = new SubChannel[8];
for (int index = 0; index < _subChannels.Length; index++) for (int index = 0; index < _subChannels.Length; index++)
{ {
_subChannels[index] = new SubChannel(); _subChannels[index] = new SubChannel();
context.Methods.RegisterCallbacks(_subChannels[index].State); _context.Methods.RegisterCallbacks(_subChannels[index].State);
} }
} }
/// <summary>
/// Send macro code/data to the MME
/// </summary>
/// <param name="index">The index in the MME</param>
/// <param name="data">The data to use</param>
public void SendMacroCodeData(int index, int data)
{
_mme[index] = data;
}
/// <summary>
/// Bind a macro index to a position for the MME
/// </summary>
/// <param name="index">The macro index</param>
/// <param name="position">The position of the macro</param>
public void BindMacro(int index, int position)
{
_macros[index] = new CachedMacro(position);
}
/// <summary>
/// Change the shadow RAM setting
/// </summary>
/// <param name="shadowCtrl">The new Shadow RAM setting</param>
public void SetMmeShadowRamControl(ShadowRamControl shadowCtrl)
{
_shadowCtrl = shadowCtrl;
}
/// <summary> /// <summary>
/// Calls a GPU method. /// Calls a GPU method.
/// </summary> /// </summary>
/// <param name="meth">GPU method call parameters</param> /// <param name="meth">GPU method call parameters</param>
public void CallMethod(MethodParams meth) public void CallMethod(MethodParams meth)
{ {
if ((NvGpuFifoMeth)meth.Method == NvGpuFifoMeth.BindChannel) if ((MethodOffset)meth.Method == MethodOffset.BindChannel)
{ {
_subChannels[meth.SubChannel].Class = (ClassId)meth.Argument; _subChannels[meth.SubChannel] = new SubChannel
{
Class = (ClassId)meth.Argument
};
_context.Methods.RegisterCallbacks(_subChannels[meth.SubChannel].State);
} }
else if (meth.Method < 0x60) else if (meth.Method < 0x60)
{ {
switch ((NvGpuFifoMeth)meth.Method) // TODO: check if macros are shared between subchannels or not. For now let's assume they are.
{ _fifoChannel.State.CallMethod(meth);
case NvGpuFifoMeth.WaitForIdle:
{
_context.Methods.PerformDeferredDraws();
_context.Renderer.Pipeline.Barrier();
break;
}
case NvGpuFifoMeth.SetMacroUploadAddress:
{
_currMacroPosition = meth.Argument;
break;
}
case NvGpuFifoMeth.SendMacroCodeData:
{
_mme[_currMacroPosition++] = meth.Argument;
break;
}
case NvGpuFifoMeth.SetMacroBindingIndex:
{
_currMacroBindIndex = meth.Argument;
break;
}
case NvGpuFifoMeth.BindMacro:
{
int position = meth.Argument;
_macros[_currMacroBindIndex++] = new CachedMacro(position);
break;
}
case NvGpuFifoMeth.SetMmeShadowRamControl:
{
_shadowCtrl = (ShadowRamControl)meth.Argument;
break;
}
}
} }
else if (meth.Method < 0xe00) else if (meth.Method < 0xe00)
{ {

View File

@ -1,16 +0,0 @@
namespace Ryujinx.Graphics.Gpu
{
/// <summary>
/// GPU commands FIFO processor commands.
/// </summary>
enum NvGpuFifoMeth
{
BindChannel = 0,
WaitForIdle = 0x44,
SetMacroUploadAddress = 0x45,
SendMacroCodeData = 0x46,
SetMacroBindingIndex = 0x47,
BindMacro = 0x48,
SetMmeShadowRamControl = 0x49
}
}

View File

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gpu.State
{
/// <summary>
/// Fence action operations.
/// </summary>
enum FenceActionOperation
{
Acquire = 0,
Increment = 1
}
}

View File

@ -8,6 +8,15 @@ namespace Ryujinx.Graphics.Gpu.State
/// </remarks> /// </remarks>
enum MethodOffset enum MethodOffset
{ {
BindChannel = 0x00,
FenceValue = 0x1c,
FenceAction = 0x1d,
WaitForIdle = 0x44,
MacroUploadAddress = 0x45,
SendMacroCodeData = 0x46,
MacroBindingIndex = 0x47,
BindMacro = 0x48,
SetMmeShadowRamControl = 0x49,
I2mParams = 0x60, I2mParams = 0x60,
LaunchDma = 0x6c, LaunchDma = 0x6c,
LoadInlineData = 0x6d, LoadInlineData = 0x6d,
@ -15,6 +24,7 @@ namespace Ryujinx.Graphics.Gpu.State
CopySrcTexture = 0x8c, CopySrcTexture = 0x8c,
DispatchParamsAddress = 0xad, DispatchParamsAddress = 0xad,
Dispatch = 0xaf, Dispatch = 0xaf,
SyncpointAction = 0xb2,
CopyBuffer = 0xc0, CopyBuffer = 0xc0,
RasterizeEnable = 0xdf, RasterizeEnable = 0xdf,
CopyBufferParams = 0x100, CopyBufferParams = 0x100,

View File

@ -5,7 +5,8 @@ namespace Ryujinx.Graphics.Gpu.State
/// </summary> /// </summary>
enum ReportMode enum ReportMode
{ {
Semaphore = 0, Release = 0,
Counter = 2 Acquire = 1,
Counter = 2
} }
} }

View File

@ -0,0 +1,134 @@
using System;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Synchronization
{
/// <summary>
/// GPU synchronization manager.
/// </summary>
public class SynchronizationManager
{
/// <summary>
/// The maximum number of syncpoints supported by the GM20B.
/// </summary>
public const int MaxHardwareSyncpoints = 192;
/// <summary>
/// Array containing all hardware syncpoints.
/// </summary>
private Syncpoint[] _syncpoints;
public SynchronizationManager()
{
_syncpoints = new Syncpoint[MaxHardwareSyncpoints];
for (uint i = 0; i < _syncpoints.Length; i++)
{
_syncpoints[i] = new Syncpoint(i);
}
}
/// <summary>
/// Increment the value of a syncpoint with a given id.
/// </summary>
/// <param name="id">The id of the syncpoint</param>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception>
/// <returns>The incremented value of the syncpoint</returns>
public uint IncrementSyncpoint(uint id)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
return _syncpoints[id].Increment();
}
/// <summary>
/// Get the value of a syncpoint with a given id.
/// </summary>
/// <param name="id">The id of the syncpoint</param>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception>
/// <returns>The value of the syncpoint</returns>
public uint GetSyncpointValue(uint id)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
return _syncpoints[id].Value;
}
/// <summary>
/// Register a new callback on a syncpoint with a given id at a target threshold.
/// The callback will be called once the threshold is reached and will automatically be unregistered.
/// </summary>
/// <param name="id">The id of the syncpoint</param>
/// <param name="threshold">The target threshold</param>
/// <param name="callback">The callback to call when the threshold is reached</param>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception>
/// <returns>The created SyncpointWaiterHandle object or null if already past threshold</returns>
public SyncpointWaiterHandle RegisterCallbackOnSyncpoint(uint id, uint threshold, Action callback)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
return _syncpoints[id].RegisterCallback(threshold, callback);
}
/// <summary>
/// Unregister a callback on a given syncpoint.
/// </summary>
/// <param name="id">The id of the syncpoint</param>
/// <param name="waiterInformation">The waiter information to unregister</param>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception>
public void UnregisterCallback(uint id, SyncpointWaiterHandle waiterInformation)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
_syncpoints[id].UnregisterCallback(waiterInformation);
}
/// <summary>
/// Wait on a syncpoint with a given id at a target threshold.
/// The callback will be called once the threshold is reached and will automatically be unregistered.
/// </summary>
/// <param name="id">The id of the syncpoint</param>
/// <param name="threshold">The target threshold</param>
/// <param name="timeout">The timeout</param>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception>
/// <returns>True if timed out</returns>
public bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
using (ManualResetEvent waitEvent = new ManualResetEvent(false))
{
var info = _syncpoints[id].RegisterCallback(threshold, () => waitEvent.Set());
if (info == null)
{
return false;
}
bool signaled = waitEvent.WaitOne(timeout);
if (!signaled && info != null)
{
_syncpoints[id].UnregisterCallback(info);
}
return !signaled;
}
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Synchronization
{
/// <summary>
/// Represents GPU hardware syncpoint.
/// </summary>
class Syncpoint
{
private int _storedValue;
public readonly uint Id;
// TODO: get rid of this lock
private object _listLock = new object();
/// <summary>
/// The value of the syncpoint.
/// </summary>
public uint Value => (uint)_storedValue;
// TODO: switch to something handling concurrency?
private List<SyncpointWaiterHandle> _waiters;
public Syncpoint(uint id)
{
Id = id;
_waiters = new List<SyncpointWaiterHandle>();
}
/// <summary>
/// Register a new callback for a target threshold.
/// The callback will be called once the threshold is reached and will automatically be unregistered.
/// </summary>
/// <param name="threshold">The target threshold</param>
/// <param name="callback">The callback to call when the threshold is reached</param>
/// <returns>The created SyncpointWaiterHandle object or null if already past threshold</returns>
public SyncpointWaiterHandle RegisterCallback(uint threshold, Action callback)
{
lock (_listLock)
{
if (Value >= threshold)
{
callback();
return null;
}
else
{
SyncpointWaiterHandle waiterInformation = new SyncpointWaiterHandle
{
Threshold = threshold,
Callback = callback
};
_waiters.Add(waiterInformation);
return waiterInformation;
}
}
}
public void UnregisterCallback(SyncpointWaiterHandle waiterInformation)
{
lock (_listLock)
{
_waiters.Remove(waiterInformation);
}
}
/// <summary>
/// Increment the syncpoint
/// </summary>
/// <returns>The incremented value of the syncpoint</returns>
public uint Increment()
{
uint currentValue = (uint)Interlocked.Increment(ref _storedValue);
lock (_listLock)
{
_waiters.RemoveAll(item =>
{
bool isPastThreshold = currentValue >= item.Threshold;
if (isPastThreshold)
{
item.Callback();
}
return isPastThreshold;
});
}
return currentValue;
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace Ryujinx.Graphics.Gpu.Synchronization
{
public class SyncpointWaiterHandle
{
internal uint Threshold;
internal Action Callback;
}
}

View File

@ -30,12 +30,17 @@ namespace Ryujinx.Graphics.Gpu
public ImageCrop Crop { get; } public ImageCrop Crop { get; }
/// <summary> /// <summary>
/// Texture release callback. /// Texture acquire callback.
/// </summary> /// </summary>
public Action<object> Callback { get; } public Action<GpuContext, object> AcquireCallback { get; }
/// <summary> /// <summary>
/// User defined object, passed to the release callback. /// Texture release callback.
/// </summary>
public Action<object> ReleaseCallback { get; }
/// <summary>
/// User defined object, passed to the various callbacks.
/// </summary> /// </summary>
public object UserObj { get; } public object UserObj { get; }
@ -44,18 +49,21 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
/// <param name="info">Information of the texture to be presented</param> /// <param name="info">Information of the texture to be presented</param>
/// <param name="crop">Texture crop region</param> /// <param name="crop">Texture crop region</param>
/// <param name="callback">Texture release callback</param> /// <param name="acquireCallback">Texture acquire callback</param>
/// <param name="releaseCallback">Texture release callback</param>
/// <param name="userObj">User defined object passed to the release callback, can be used to identify the texture</param> /// <param name="userObj">User defined object passed to the release callback, can be used to identify the texture</param>
public PresentationTexture( public PresentationTexture(
TextureInfo info, TextureInfo info,
ImageCrop crop, ImageCrop crop,
Action<object> callback, Action<GpuContext, object> acquireCallback,
object userObj) Action<object> releaseCallback,
object userObj)
{ {
Info = info; Info = info;
Crop = crop; Crop = crop;
Callback = callback; AcquireCallback = acquireCallback;
UserObj = userObj; ReleaseCallback = releaseCallback;
UserObj = userObj;
} }
} }
@ -87,20 +95,22 @@ namespace Ryujinx.Graphics.Gpu
/// <param name="format">Texture format</param> /// <param name="format">Texture format</param>
/// <param name="bytesPerPixel">Texture format bytes per pixel (must match the format)</param> /// <param name="bytesPerPixel">Texture format bytes per pixel (must match the format)</param>
/// <param name="crop">Texture crop region</param> /// <param name="crop">Texture crop region</param>
/// <param name="callback">Texture release callback</param> /// <param name="acquireCallback">Texture acquire callback</param>
/// <param name="releaseCallback">Texture release callback</param>
/// <param name="userObj">User defined object passed to the release callback</param> /// <param name="userObj">User defined object passed to the release callback</param>
public void EnqueueFrameThreadSafe( public void EnqueueFrameThreadSafe(
ulong address, ulong address,
int width, int width,
int height, int height,
int stride, int stride,
bool isLinear, bool isLinear,
int gobBlocksInY, int gobBlocksInY,
Format format, Format format,
int bytesPerPixel, int bytesPerPixel,
ImageCrop crop, ImageCrop crop,
Action<object> callback, Action<GpuContext, object> acquireCallback,
object userObj) Action<object> releaseCallback,
object userObj)
{ {
FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel); FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel);
@ -120,7 +130,7 @@ namespace Ryujinx.Graphics.Gpu
Target.Texture2D, Target.Texture2D,
formatInfo); formatInfo);
_frameQueue.Enqueue(new PresentationTexture(info, crop, callback, userObj)); _frameQueue.Enqueue(new PresentationTexture(info, crop, acquireCallback, releaseCallback, userObj));
} }
/// <summary> /// <summary>
@ -134,6 +144,8 @@ namespace Ryujinx.Graphics.Gpu
if (_frameQueue.TryDequeue(out PresentationTexture pt)) if (_frameQueue.TryDequeue(out PresentationTexture pt))
{ {
pt.AcquireCallback(_context, pt.UserObj);
Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(pt.Info); Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(pt.Info);
texture.SynchronizeMemory(); texture.SynchronizeMemory();
@ -142,7 +154,7 @@ namespace Ryujinx.Graphics.Gpu
swapBuffersCallback(); swapBuffersCallback();
pt.Callback(pt.UserObj); pt.ReleaseCallback(pt.UserObj);
} }
} }
} }

View File

@ -17,9 +17,11 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc; using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
using Ryujinx.HLE.HOS.Services.Settings; using Ryujinx.HLE.HOS.Services.Settings;
using Ryujinx.HLE.HOS.Services.Sm; using Ryujinx.HLE.HOS.Services.Sm;
using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Executables;
@ -39,6 +41,7 @@ using TimeServiceManager = Ryujinx.HLE.HOS.Services.Time.TimeManager;
using NsoExecutable = Ryujinx.HLE.Loaders.Executables.NsoExecutable; using NsoExecutable = Ryujinx.HLE.Loaders.Executables.NsoExecutable;
using static LibHac.Fs.ApplicationSaveDataManagement; using static LibHac.Fs.ApplicationSaveDataManagement;
using Ryujinx.HLE.HOS.Services.Nv;
namespace Ryujinx.HLE.HOS namespace Ryujinx.HLE.HOS
{ {
@ -131,6 +134,8 @@ namespace Ryujinx.HLE.HOS
internal long HidBaseAddress { get; private set; } internal long HidBaseAddress { get; private set; }
internal NvHostSyncpt HostSyncpoint { get; private set; }
public Horizon(Switch device, ContentManager contentManager) public Horizon(Switch device, ContentManager contentManager)
{ {
ControlData = new BlitStruct<ApplicationControlProperty>(1); ControlData = new BlitStruct<ApplicationControlProperty>(1);
@ -259,6 +264,8 @@ namespace Ryujinx.HLE.HOS
TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
DatabaseImpl.Instance.InitializeDatabase(device); DatabaseImpl.Instance.InitializeDatabase(device);
HostSyncpoint = new NvHostSyncpt(device);
} }
public void LoadCart(string exeFsDir, string romFsFile = null) public void LoadCart(string exeFsDir, string romFsFile = null)
@ -870,6 +877,10 @@ namespace Ryujinx.HLE.HOS
Device.VsyncEvent.Set(); Device.VsyncEvent.Set();
} }
// 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.
INvDrvServices.Destroy();
// This is needed as the IPC Dummy KThread is also counted in the ThreadCounter. // This is needed as the IPC Dummy KThread is also counted in the ThreadCounter.
ThreadCounter.Signal(); ThreadCounter.Signal();

View File

@ -8,6 +8,8 @@ namespace Ryujinx.HLE.HOS
{ {
private ConcurrentDictionary<int, object> _objs; private ConcurrentDictionary<int, object> _objs;
public ICollection<object> Values => _objs.Values;
public IdDictionary() public IdDictionary()
{ {
_objs = new ConcurrentDictionary<int, object>(); _objs = new ConcurrentDictionary<int, object>();

View File

@ -1,21 +1,30 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Mm.Types;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Mm namespace Ryujinx.HLE.HOS.Services.Mm
{ {
[Service("mm:u")] [Service("mm:u")]
class IRequest : IpcService class IRequest : IpcService
{ {
public IRequest(ServiceCtx context) { } private static object _sessionListLock = new object();
private static List<MultiMediaSession> _sessionList = new List<MultiMediaSession>();
private static uint _uniqueId = 1;
public IRequest(ServiceCtx context) {}
[Command(0)] [Command(0)]
// InitializeOld(u32, u32, u32) // InitializeOld(u32, u32, u32)
public ResultCode InitializeOld(ServiceCtx context) public ResultCode InitializeOld(ServiceCtx context)
{ {
int unknown0 = context.RequestData.ReadInt32(); MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32();
int unknown1 = context.RequestData.ReadInt32(); int fgmId = context.RequestData.ReadInt32();
int unknown2 = context.RequestData.ReadInt32(); bool isAutoClearEvent = context.RequestData.ReadInt32() != 0;
Logger.PrintStub(LogClass.ServiceMm, new { unknown0, unknown1, unknown2 }); Logger.PrintStub(LogClass.ServiceMm, new { operationType, fgmId, isAutoClearEvent });
Register(operationType, fgmId, isAutoClearEvent);
return ResultCode.Success; return ResultCode.Success;
} }
@ -24,7 +33,14 @@ namespace Ryujinx.HLE.HOS.Services.Mm
// FinalizeOld(u32) // FinalizeOld(u32)
public ResultCode FinalizeOld(ServiceCtx context) public ResultCode FinalizeOld(ServiceCtx context)
{ {
Logger.PrintStub(LogClass.ServiceMm); MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32();
Logger.PrintStub(LogClass.ServiceMm, new { operationType });
lock (_sessionListLock)
{
_sessionList.Remove(GetSessionByType(operationType));
}
return ResultCode.Success; return ResultCode.Success;
} }
@ -33,11 +49,17 @@ namespace Ryujinx.HLE.HOS.Services.Mm
// SetAndWaitOld(u32, u32, u32) // SetAndWaitOld(u32, u32, u32)
public ResultCode SetAndWaitOld(ServiceCtx context) public ResultCode SetAndWaitOld(ServiceCtx context)
{ {
int unknown0 = context.RequestData.ReadInt32(); MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32();
int unknown1 = context.RequestData.ReadInt32(); uint value = context.RequestData.ReadUInt32();
int unknown2 = context.RequestData.ReadInt32(); int timeout = context.RequestData.ReadInt32();
Logger.PrintStub(LogClass.ServiceMm, new { operationType, value, timeout });
lock (_sessionListLock)
{
GetSessionByType(operationType)?.SetAndWait(value, timeout);
}
Logger.PrintStub(LogClass.ServiceMm, new { unknown0, unknown1, unknown2 });
return ResultCode.Success; return ResultCode.Success;
} }
@ -45,20 +67,35 @@ namespace Ryujinx.HLE.HOS.Services.Mm
// GetOld(u32) -> u32 // GetOld(u32) -> u32
public ResultCode GetOld(ServiceCtx context) public ResultCode GetOld(ServiceCtx context)
{ {
int unknown0 = context.RequestData.ReadInt32(); MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32();
Logger.PrintStub(LogClass.ServiceMm, new { unknown0 }); Logger.PrintStub(LogClass.ServiceMm, new { operationType });
context.ResponseData.Write(0); lock (_sessionListLock)
{
MultiMediaSession session = GetSessionByType(operationType);
uint currentValue = session == null ? 0 : session.CurrentValue;
context.ResponseData.Write(currentValue);
}
return ResultCode.Success; return ResultCode.Success;
} }
[Command(4)] [Command(4)]
// Initialize() // Initialize(u32, u32, u32) -> u32
public ResultCode Initialize(ServiceCtx context) public ResultCode Initialize(ServiceCtx context)
{ {
Logger.PrintStub(LogClass.ServiceMm); MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32();
int fgmId = context.RequestData.ReadInt32();
bool isAutoClearEvent = context.RequestData.ReadInt32() != 0;
Logger.PrintStub(LogClass.ServiceMm, new { operationType, fgmId, isAutoClearEvent });
uint id = Register(operationType, fgmId, isAutoClearEvent);
context.ResponseData.Write(id);
return ResultCode.Success; return ResultCode.Success;
} }
@ -67,7 +104,14 @@ namespace Ryujinx.HLE.HOS.Services.Mm
// Finalize(u32) // Finalize(u32)
public ResultCode Finalize(ServiceCtx context) public ResultCode Finalize(ServiceCtx context)
{ {
Logger.PrintStub(LogClass.ServiceMm); uint id = context.RequestData.ReadUInt32();
Logger.PrintStub(LogClass.ServiceMm, new { id });
lock (_sessionListLock)
{
_sessionList.Remove(GetSessionById(id));
}
return ResultCode.Success; return ResultCode.Success;
} }
@ -76,11 +120,16 @@ namespace Ryujinx.HLE.HOS.Services.Mm
// SetAndWait(u32, u32, u32) // SetAndWait(u32, u32, u32)
public ResultCode SetAndWait(ServiceCtx context) public ResultCode SetAndWait(ServiceCtx context)
{ {
int unknown0 = context.RequestData.ReadInt32(); uint id = context.RequestData.ReadUInt32();
int unknown1 = context.RequestData.ReadInt32(); uint value = context.RequestData.ReadUInt32();
int unknown2 = context.RequestData.ReadInt32(); int timeout = context.RequestData.ReadInt32();
Logger.PrintStub(LogClass.ServiceMm, new { unknown0, unknown1, unknown2 }); Logger.PrintStub(LogClass.ServiceMm, new { id, value, timeout });
lock (_sessionListLock)
{
GetSessionById(id)?.SetAndWait(value, timeout);
}
return ResultCode.Success; return ResultCode.Success;
} }
@ -89,13 +138,59 @@ namespace Ryujinx.HLE.HOS.Services.Mm
// Get(u32) -> u32 // Get(u32) -> u32
public ResultCode Get(ServiceCtx context) public ResultCode Get(ServiceCtx context)
{ {
int unknown0 = context.RequestData.ReadInt32(); uint id = context.RequestData.ReadUInt32();
Logger.PrintStub(LogClass.ServiceMm, new { unknown0 }); Logger.PrintStub(LogClass.ServiceMm, new { id });
context.ResponseData.Write(0); lock (_sessionListLock)
{
MultiMediaSession session = GetSessionById(id);
uint currentValue = session == null ? 0 : session.CurrentValue;
context.ResponseData.Write(currentValue);
}
return ResultCode.Success; return ResultCode.Success;
} }
private MultiMediaSession GetSessionById(uint id)
{
foreach (MultiMediaSession session in _sessionList)
{
if (session.Id == id)
{
return session;
}
}
return null;
}
private MultiMediaSession GetSessionByType(MultiMediaOperationType type)
{
foreach (MultiMediaSession session in _sessionList)
{
if (session.Type == type)
{
return session;
}
}
return null;
}
private uint Register(MultiMediaOperationType type, int fgmId, bool isAutoClearEvent)
{
lock (_sessionListLock)
{
// Nintendo ignore the fgm id as the other interfaces were deprecated.
MultiMediaSession session = new MultiMediaSession(_uniqueId++, type, isAutoClearEvent);
_sessionList.Add(session);
return session.Id;
}
}
} }
} }

View File

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Mm.Types
{
enum MultiMediaOperationType : uint
{
// TODO: figure out the unknown variants.
Unknown2 = 2,
VideoDecode = 5,
VideoEncode = 6,
Unknown7 = 7
}
}

View File

@ -0,0 +1,24 @@
namespace Ryujinx.HLE.HOS.Services.Mm.Types
{
class MultiMediaSession
{
public MultiMediaOperationType Type { get; }
public bool IsAutoClearEvent { get; }
public uint Id { get; }
public uint CurrentValue { get; private set; }
public MultiMediaSession(uint id, MultiMediaOperationType type, bool isAutoClearEvent)
{
Type = type;
Id = id;
IsAutoClearEvent = isAutoClearEvent;
CurrentValue = 0;
}
public void SetAndWait(uint value, int timeout)
{
CurrentValue = value;
}
}
}

View File

@ -264,7 +264,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = ConvertInternalErrorCode(internalResult); errorCode = ConvertInternalErrorCode(internalResult);
if (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{ {
context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
} }
@ -452,7 +452,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = ConvertInternalErrorCode(internalResult); errorCode = ConvertInternalErrorCode(internalResult);
if (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{ {
context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
} }
@ -497,7 +497,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = ConvertInternalErrorCode(internalResult); errorCode = ConvertInternalErrorCode(internalResult);
if (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{ {
context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.WriteBytes(inlineOutBufferPosition, inlineOutBuffer.ToArray()); context.Memory.WriteBytes(inlineOutBufferPosition, inlineOutBuffer.ToArray());
@ -519,5 +519,17 @@ namespace Ryujinx.HLE.HOS.Services.Nv
return ResultCode.Success; return ResultCode.Success;
} }
public static void Destroy()
{
foreach (object entry in _deviceFileIdRegistry.Values)
{
NvDeviceFile deviceFile = (NvDeviceFile)entry;
deviceFile.Close();
}
_deviceFileIdRegistry.Clear();
}
} }
} }

View File

@ -1,8 +1,9 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -12,21 +13,42 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
{ {
class NvHostChannelDeviceFile : NvDeviceFile class NvHostChannelDeviceFile : NvDeviceFile
{ {
private const uint MaxModuleSyncpoint = 16;
private uint _timeout; private uint _timeout;
private uint _submitTimeout; private uint _submitTimeout;
private uint _timeslice; private uint _timeslice;
private GpuContext _gpu; private Switch _device;
private ARMeilleure.Memory.MemoryManager _memory; private ARMeilleure.Memory.MemoryManager _memory;
public enum ResourcePolicy
{
Device,
Channel
}
protected static uint[] DeviceSyncpoints = new uint[MaxModuleSyncpoint];
protected uint[] ChannelSyncpoints;
protected static ResourcePolicy ChannelResourcePolicy = ResourcePolicy.Device;
private NvFence _channelSyncpoint;
public NvHostChannelDeviceFile(ServiceCtx context) : base(context) public NvHostChannelDeviceFile(ServiceCtx context) : base(context)
{ {
_gpu = context.Device.Gpu; _device = context.Device;
_memory = context.Memory; _memory = context.Memory;
_timeout = 3000; _timeout = 3000;
_submitTimeout = 0; _submitTimeout = 0;
_timeslice = 0; _timeslice = 0;
ChannelSyncpoints = new uint[MaxModuleSyncpoint];
_channelSyncpoint.Id = _device.System.HostSyncpoint.AllocateSyncpoint(false);
_channelSyncpoint.UpdateValue(_device.System.HostSyncpoint);
} }
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
@ -132,9 +154,24 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments) private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments)
{ {
arguments.Value = 0; if (arguments.Parameter >= MaxModuleSyncpoint)
{
return NvInternalResult.InvalidInput;
}
Logger.PrintStub(LogClass.ServiceNv); if (ChannelResourcePolicy == ResourcePolicy.Device)
{
arguments.Value = GetSyncpointDevice(_device.System.HostSyncpoint, arguments.Parameter, false);
}
else
{
arguments.Value = GetSyncpointChannel(arguments.Parameter, false);
}
if (arguments.Value == 0)
{
return NvInternalResult.TryAgain;
}
return NvInternalResult.Success; return NvInternalResult.Success;
} }
@ -293,6 +330,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments) private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments)
{ {
_channelSyncpoint.UpdateValue(_device.System.HostSyncpoint);
arguments.Fence = _channelSyncpoint;
Logger.PrintStub(LogClass.ServiceNv); Logger.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success; return NvInternalResult.Success;
@ -300,6 +341,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments) private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments)
{ {
_channelSyncpoint.UpdateValue(_device.System.HostSyncpoint);
arguments.Fence = _channelSyncpoint;
Logger.PrintStub(LogClass.ServiceNv); Logger.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success; return NvInternalResult.Success;
@ -330,17 +375,125 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span<ulong> entries) protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span<ulong> entries)
{ {
foreach (ulong entry in entries) if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue))
{ {
_gpu.DmaPusher.Push(entry); return NvInternalResult.InvalidInput;
} }
header.Fence.Id = 0; if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value))
header.Fence.Value = 0; {
_device.Gpu.DmaPusher.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence));
}
_device.Gpu.DmaPusher.PushEntries(entries);
header.Fence.Id = _channelSyncpoint.Id;
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) || header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue))
{
uint incrementCount = header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) ? 2u : 0u;
if (header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue))
{
incrementCount += header.Fence.Value;
}
header.Fence.Value = _device.System.HostSyncpoint.IncrementSyncpointMaxExt(header.Fence.Id, (int)incrementCount);
}
else
{
header.Fence.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(header.Fence.Id);
}
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement))
{
_device.Gpu.DmaPusher.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags));
}
header.Flags = SubmitGpfifoFlags.None;
_device.Gpu.DmaPusher.SignalNewEntries();
return NvInternalResult.Success; return NvInternalResult.Success;
} }
public uint GetSyncpointChannel(uint index, bool isClientManaged)
{
if (ChannelSyncpoints[index] != 0)
{
return ChannelSyncpoints[index];
}
ChannelSyncpoints[index] = _device.System.HostSyncpoint.AllocateSyncpoint(isClientManaged);
return ChannelSyncpoints[index];
}
public static uint GetSyncpointDevice(NvHostSyncpt syncpointManager, uint index, bool isClientManaged)
{
if (DeviceSyncpoints[index] != 0)
{
return DeviceSyncpoints[index];
}
DeviceSyncpoints[index] = syncpointManager.AllocateSyncpoint(isClientManaged);
return DeviceSyncpoints[index];
}
private static int[] CreateWaitCommandBuffer(NvFence fence)
{
int[] commandBuffer = new int[4];
// SyncpointValue = fence.Value;
commandBuffer[0] = 0x2001001C;
commandBuffer[1] = (int)fence.Value;
// SyncpointAction(fence.id, increment: false, switch_en: true);
commandBuffer[2] = 0x2001001D;
commandBuffer[3] = (((int)fence.Id << 8) | (0 << 0) | (1 << 4));
return commandBuffer;
}
private int[] CreateIncrementCommandBuffer(ref NvFence fence, SubmitGpfifoFlags flags)
{
bool hasWfi = !flags.HasFlag(SubmitGpfifoFlags.SuppressWfi);
int[] commandBuffer;
int offset = 0;
if (hasWfi)
{
commandBuffer = new int[8];
// WaitForInterrupt(handle)
commandBuffer[offset++] = 0x2001001E;
commandBuffer[offset++] = 0x0;
}
else
{
commandBuffer = new int[6];
}
// SyncpointValue = 0x0;
commandBuffer[offset++] = 0x2001001C;
commandBuffer[offset++] = 0x0;
// Increment the syncpoint 2 times. (mitigate a hardware bug)
// SyncpointAction(fence.id, increment: true, switch_en: false);
commandBuffer[offset++] = 0x2001001D;
commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4));
// SyncpointAction(fence.id, increment: true, switch_en: false);
commandBuffer[offset++] = 0x2001001D;
commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4));
return commandBuffer;
}
public override void Close() { } public override void Close() { }
} }
} }

View File

@ -6,9 +6,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
struct SubmitGpfifoArguments struct SubmitGpfifoArguments
{ {
public long Address; public long Address;
public int NumEntries; public int NumEntries;
public int Flags; public SubmitGpfifoFlags Flags;
public NvFence Fence; public NvFence Fence;
} }
} }

View File

@ -0,0 +1,15 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
{
[Flags]
enum SubmitGpfifoFlags : uint
{
None,
FenceWait = 1 << 0,
FenceIncrement = 1 << 1,
HwFormat = 1 << 2,
SuppressWfi = 1 << 4,
IncrementWithValue = 1 << 8,
}
}

View File

@ -1,4 +1,5 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types;
@ -13,12 +14,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{ {
internal class NvHostCtrlDeviceFile : NvDeviceFile internal class NvHostCtrlDeviceFile : NvDeviceFile
{ {
private const int EventsCount = 64; public const int EventsCount = 64;
private bool _isProductionMode; private bool _isProductionMode;
private NvHostSyncpt _syncpt; private Switch _device;
private NvHostEvent[] _events; private NvHostEvent[] _events;
private KEvent _dummyEvent;
public NvHostCtrlDeviceFile(ServiceCtx context) : base(context) public NvHostCtrlDeviceFile(ServiceCtx context) : base(context)
{ {
@ -31,9 +31,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
_isProductionMode = true; _isProductionMode = true;
} }
_syncpt = new NvHostSyncpt(); _device = context.Device;
_events = new NvHostEvent[EventsCount];
_dummyEvent = new KEvent(context.Device.System); _events = new NvHostEvent[EventsCount];
} }
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
@ -69,6 +69,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
configArgument.CopyTo(arguments); configArgument.CopyTo(arguments);
} }
break; break;
case 0x1c:
result = CallIoctlMethod<uint>(EventSignal, arguments);
break;
case 0x1d: case 0x1d:
result = CallIoctlMethod<EventWaitArguments>(EventWait, arguments); result = CallIoctlMethod<EventWaitArguments>(EventWait, arguments);
break; break;
@ -78,16 +81,45 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
case 0x1f: case 0x1f:
result = CallIoctlMethod<uint>(EventRegister, arguments); result = CallIoctlMethod<uint>(EventRegister, arguments);
break; break;
case 0x20:
result = CallIoctlMethod<uint>(EventUnregister, arguments);
break;
case 0x21:
result = CallIoctlMethod<ulong>(EventKill, arguments);
break;
} }
} }
return result; return result;
} }
private KEvent QueryEvent(uint eventId)
{
uint eventSlot;
uint syncpointId;
if ((eventId >> 28) == 1)
{
eventSlot = eventId & 0xFFFF;
syncpointId = (eventId >> 16) & 0xFFF;
}
else
{
eventSlot = eventId & 0xFF;
syncpointId = eventId >> 4;
}
if (eventSlot >= EventsCount || _events[eventSlot] == null || _events[eventSlot].Fence.Id != syncpointId)
{
return null;
}
return _events[eventSlot].Event;
}
public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) public override NvInternalResult QueryEvent(out int eventHandle, uint eventId)
{ {
// TODO: implement SyncPts <=> KEvent logic accurately. For now we return a dummy event. KEvent targetEvent = QueryEvent(eventId);
KEvent targetEvent = _dummyEvent;
if (targetEvent != null) if (targetEvent != null)
{ {
@ -113,24 +145,26 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
private NvInternalResult SyncptIncr(ref uint id) private NvInternalResult SyncptIncr(ref uint id)
{ {
if (id >= NvHostSyncpt.SyncptsCount) if (id >= SynchronizationManager.MaxHardwareSyncpoints)
{ {
return NvInternalResult.InvalidInput; return NvInternalResult.InvalidInput;
} }
_syncpt.Increment((int)id); _device.System.HostSyncpoint.Increment(id);
return NvInternalResult.Success; return NvInternalResult.Success;
} }
private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments) private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments)
{ {
return SyncptWait(ref arguments, out _); uint dummyValue = 0;
return EventWait(ref arguments.Fence, ref dummyValue, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false);
} }
private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments) private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments)
{ {
return SyncptWait(ref arguments.Input, out arguments.Value); return EventWait(ref arguments.Input.Fence, ref arguments.Value, arguments.Input.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false);
} }
private NvInternalResult SyncptReadMax(ref NvFence arguments) private NvInternalResult SyncptReadMax(ref NvFence arguments)
@ -182,194 +216,237 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
private NvInternalResult EventWait(ref EventWaitArguments arguments) private NvInternalResult EventWait(ref EventWaitArguments arguments)
{ {
return EventWait(ref arguments, async: false); return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: true);
} }
private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments) private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments)
{ {
return EventWait(ref arguments, async: true); return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: true, isWaitEventCmd: false);
} }
private NvInternalResult EventRegister(ref uint userEventId) private NvInternalResult EventRegister(ref uint userEventId)
{ {
Logger.PrintStub(LogClass.ServiceNv); NvInternalResult result = EventUnregister(ref userEventId);
if (result == NvInternalResult.Success)
{
_events[userEventId] = new NvHostEvent(_device.System.HostSyncpoint, userEventId, _device.System);
}
return result;
}
private NvInternalResult EventUnregister(ref uint userEventId)
{
if (userEventId >= EventsCount)
{
return NvInternalResult.InvalidInput;
}
NvHostEvent hostEvent = _events[userEventId];
if (hostEvent == null)
{
return NvInternalResult.Success;
}
if (hostEvent.State == NvHostEventState.Available ||
hostEvent.State == NvHostEventState.Cancelled ||
hostEvent.State == NvHostEventState.Signaled)
{
_events[userEventId].Dispose();
_events[userEventId] = null;
return NvInternalResult.Success;
}
return NvInternalResult.Busy;
}
private NvInternalResult EventKill(ref ulong eventMask)
{
NvInternalResult result = NvInternalResult.Success;
for (uint eventId = 0; eventId < EventsCount; eventId++)
{
if ((eventMask & (1UL << (int)eventId)) != 0)
{
NvInternalResult tmp = EventUnregister(ref eventId);
if (tmp != NvInternalResult.Success)
{
result = tmp;
}
}
}
return result;
}
private NvInternalResult EventSignal(ref uint userEventId)
{
uint eventId = userEventId & ushort.MaxValue;
if (eventId >= EventsCount)
{
return NvInternalResult.InvalidInput;
}
NvHostEvent hostEvent = _events[eventId];
if (hostEvent == null)
{
return NvInternalResult.InvalidInput;
}
NvHostEventState oldState = hostEvent.State;
if (oldState == NvHostEventState.Waiting)
{
hostEvent.State = NvHostEventState.Cancelling;
hostEvent.Cancel(_device.Gpu);
}
hostEvent.State = NvHostEventState.Cancelled;
return NvInternalResult.Success; return NvInternalResult.Success;
} }
private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max) private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max)
{ {
if (arguments.Id >= NvHostSyncpt.SyncptsCount) if (arguments.Id >= SynchronizationManager.MaxHardwareSyncpoints)
{ {
return NvInternalResult.InvalidInput; return NvInternalResult.InvalidInput;
} }
if (max) if (max)
{ {
arguments.Value = (uint)_syncpt.GetMax((int)arguments.Id); arguments.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(arguments.Id);
} }
else else
{ {
arguments.Value = (uint)_syncpt.GetMin((int)arguments.Id); arguments.Value = _device.System.HostSyncpoint.ReadSyncpointValue(arguments.Id);
} }
return NvInternalResult.Success; return NvInternalResult.Success;
} }
private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments, out int value) private NvInternalResult EventWait(ref NvFence fence, ref uint value, int timeout, bool isWaitEventAsyncCmd, bool isWaitEventCmd)
{ {
if (arguments.Id >= NvHostSyncpt.SyncptsCount) if (fence.Id >= SynchronizationManager.MaxHardwareSyncpoints)
{
value = 0;
return NvInternalResult.InvalidInput;
}
NvInternalResult result;
if (_syncpt.MinCompare((int)arguments.Id, arguments.Thresh))
{
result = NvInternalResult.Success;
}
else if (arguments.Timeout == 0)
{
result = NvInternalResult.TryAgain;
}
else
{
Logger.PrintDebug(LogClass.ServiceNv, $"Waiting syncpt with timeout of {arguments.Timeout}ms...");
using (ManualResetEvent waitEvent = new ManualResetEvent(false))
{
_syncpt.AddWaiter(arguments.Thresh, waitEvent);
// Note: Negative (> INT_MAX) timeouts aren't valid on .NET,
// in this case we just use the maximum timeout possible.
int timeout = arguments.Timeout;
if (timeout < -1)
{
timeout = int.MaxValue;
}
if (timeout == -1)
{
waitEvent.WaitOne();
result = NvInternalResult.Success;
}
else if (waitEvent.WaitOne(timeout))
{
result = NvInternalResult.Success;
}
else
{
result = NvInternalResult.TimedOut;
}
}
Logger.PrintDebug(LogClass.ServiceNv, "Resuming...");
}
value = _syncpt.GetMin((int)arguments.Id);
return result;
}
private NvInternalResult EventWait(ref EventWaitArguments arguments, bool async)
{
if (arguments.Id >= NvHostSyncpt.SyncptsCount)
{ {
return NvInternalResult.InvalidInput; return NvInternalResult.InvalidInput;
} }
if (_syncpt.MinCompare(arguments.Id, arguments.Thresh)) // First try to check if the syncpoint is already expired on the CPU side
if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value))
{ {
arguments.Value = _syncpt.GetMin(arguments.Id); value = _device.System.HostSyncpoint.ReadSyncpointMinValue(fence.Id);
return NvInternalResult.Success; return NvInternalResult.Success;
} }
if (!async) // Try to invalidate the CPU cache and check for expiration again.
uint newCachedSyncpointValue = _device.System.HostSyncpoint.UpdateMin(fence.Id);
// Has the fence already expired?
if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value))
{ {
arguments.Value = 0; value = newCachedSyncpointValue;
return NvInternalResult.Success;
} }
if (arguments.Timeout == 0) // If the timeout is 0, directly return.
if (timeout == 0)
{ {
return NvInternalResult.TryAgain; return NvInternalResult.TryAgain;
} }
NvHostEvent Event; // The syncpoint value isn't at the fence yet, we need to wait.
if (!isWaitEventAsyncCmd)
{
value = 0;
}
NvHostEvent hostEvent;
NvInternalResult result; NvInternalResult result;
int eventIndex; uint eventIndex;
if (async) if (isWaitEventAsyncCmd)
{ {
eventIndex = arguments.Value; eventIndex = value;
if ((uint)eventIndex >= EventsCount) if (eventIndex >= EventsCount)
{ {
return NvInternalResult.InvalidInput; return NvInternalResult.InvalidInput;
} }
Event = _events[eventIndex]; hostEvent = _events[eventIndex];
} }
else else
{ {
Event = GetFreeEvent(arguments.Id, out eventIndex); hostEvent = GetFreeEvent(fence.Id, out eventIndex);
} }
if (Event != null && if (hostEvent != null &&
(Event.State == NvHostEventState.Registered || (hostEvent.State == NvHostEventState.Available ||
Event.State == NvHostEventState.Free)) hostEvent.State == NvHostEventState.Signaled ||
hostEvent.State == NvHostEventState.Cancelled))
{ {
Event.Id = arguments.Id; hostEvent.Wait(_device.Gpu, fence);
Event.Thresh = arguments.Thresh;
Event.State = NvHostEventState.Waiting; if (isWaitEventCmd)
if (!async)
{ {
arguments.Value = ((arguments.Id & 0xfff) << 16) | 0x10000000; value = ((fence.Id & 0xfff) << 16) | 0x10000000;
} }
else else
{ {
arguments.Value = arguments.Id << 4; value = fence.Id << 4;
} }
arguments.Value |= eventIndex; value |= eventIndex;
result = NvInternalResult.TryAgain; result = NvInternalResult.TryAgain;
} }
else else
{ {
Logger.PrintError(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})");
if (hostEvent != null)
{
Logger.PrintError(LogClass.ServiceNv, hostEvent.DumpState(_device.Gpu));
}
result = NvInternalResult.InvalidInput; result = NvInternalResult.InvalidInput;
} }
return result; return result;
} }
private NvHostEvent GetFreeEvent(int id, out int eventIndex) public NvHostEvent GetFreeEvent(uint id, out uint eventIndex)
{ {
eventIndex = EventsCount; eventIndex = EventsCount;
int nullIndex = EventsCount; uint nullIndex = EventsCount;
for (int index = 0; index < EventsCount; index++) for (uint index = 0; index < EventsCount; index++)
{ {
NvHostEvent Event = _events[index]; NvHostEvent Event = _events[index];
if (Event != null) if (Event != null)
{ {
if (Event.State == NvHostEventState.Registered || if (Event.State == NvHostEventState.Available ||
Event.State == NvHostEventState.Free) Event.State == NvHostEventState.Signaled ||
Event.State == NvHostEventState.Cancelled)
{ {
eventIndex = index; eventIndex = index;
if (Event.Id == id) if (Event.Fence.Id == id)
{ {
return Event; return Event;
} }
@ -385,7 +462,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{ {
eventIndex = nullIndex; eventIndex = nullIndex;
return _events[nullIndex] = new NvHostEvent(); EventRegister(ref eventIndex);
return _events[nullIndex];
} }
if (eventIndex < EventsCount) if (eventIndex < EventsCount)
@ -396,6 +475,44 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
return null; return null;
} }
public override void Close() { } public override void Close()
{
Logger.PrintWarning(LogClass.ServiceNv, "Closing channel");
// If the device file need to be closed, cancel all user events and dispose events.
for (int i = 0; i < _events.Length; i++)
{
NvHostEvent evnt = _events[i];
if (evnt != null)
{
if (evnt.State == NvHostEventState.Waiting)
{
evnt.State = NvHostEventState.Cancelling;
evnt.Cancel(_device.Gpu);
}
else if (evnt.State == NvHostEventState.Signaling)
{
// Wait at max 9ms if the guest app is trying to signal the event while closing it..
int retryCount = 0;
do
{
if (retryCount++ > 9)
{
break;
}
// TODO: This should be handled by the kernel (reschedule the current thread ect), waiting for Kernel decoupling work.
Thread.Sleep(1);
} while (evnt.State != NvHostEventState.Signaled);
}
evnt.Dispose();
_events[i] = null;
}
}
}
} }
} }

View File

@ -1,13 +1,13 @@
using System.Runtime.InteropServices; using Ryujinx.HLE.HOS.Services.Nv.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
struct EventWaitArguments struct EventWaitArguments
{ {
public int Id; public NvFence Fence;
public int Thresh; public int Timeout;
public int Timeout; public uint Value;
public int Value;
} }
} }

View File

@ -1,10 +1,101 @@
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{ {
class NvHostEvent class NvHostEvent : IDisposable
{ {
public int Id; public NvFence Fence;
public int Thresh;
public NvHostEventState State; public NvHostEventState State;
public KEvent Event;
private uint _eventId;
private NvHostSyncpt _syncpointManager;
private SyncpointWaiterHandle _waiterInformation;
public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system)
{
Fence.Id = 0;
State = NvHostEventState.Available;
Event = new KEvent(system);
_eventId = eventId;
_syncpointManager = syncpointManager;
}
public void Reset()
{
Fence.Id = NvFence.InvalidSyncPointId;
Fence.Value = 0;
State = NvHostEventState.Available;
}
private void Signal()
{
NvHostEventState oldState = State;
State = NvHostEventState.Signaling;
if (oldState == NvHostEventState.Waiting)
{
Event.WritableEvent.Signal();
}
State = NvHostEventState.Signaled;
}
private void GpuSignaled()
{
Signal();
}
public void Cancel(GpuContext gpuContext)
{
if (_waiterInformation != null)
{
gpuContext.Synchronization.UnregisterCallback(Fence.Id, _waiterInformation);
Signal();
}
Event.WritableEvent.Clear();
}
public void Wait(GpuContext gpuContext, NvFence fence)
{
Fence = fence;
State = NvHostEventState.Waiting;
_waiterInformation = gpuContext.Synchronization.RegisterCallbackOnSyncpoint(Fence.Id, Fence.Value, GpuSignaled);
}
public string DumpState(GpuContext gpuContext)
{
string res = $"\nNvHostEvent {_eventId}:\n";
res += $"\tState: {State}\n";
if (State == NvHostEventState.Waiting)
{
res += "\tFence:\n";
res += $"\t\tId : {Fence.Id}\n";
res += $"\t\tThreshold : {Fence.Value}\n";
res += $"\t\tCurrent Value : {gpuContext.Synchronization.GetSyncpointValue(Fence.Id)}\n";
res += $"\t\tWaiter Valid : {_waiterInformation != null}\n";
}
return res;
}
public void Dispose()
{
Event.ReadableEvent.DecrementReferenceCount();
Event.WritableEvent.DecrementReferenceCount();
}
} }
} }

View File

@ -2,9 +2,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{ {
enum NvHostEventState enum NvHostEventState
{ {
Registered = 0, Available = 0,
Waiting = 1, Waiting = 1,
Busy = 2, Cancelling = 2,
Free = 5 Signaling = 3,
Signaled = 4,
Cancelled = 5
} }
} }

View File

@ -1,100 +1,181 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading; using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{ {
class NvHostSyncpt class NvHostSyncpt
{ {
public const int SyncptsCount = 192; private int[] _counterMin;
private int[] _counterMax;
private bool[] _clientManaged;
private bool[] _assigned;
private int[] _counterMin; private Switch _device;
private int[] _counterMax;
private long _eventMask; private object _syncpointAllocatorLock = new object();
private ConcurrentDictionary<EventWaitHandle, int> _waiters; public NvHostSyncpt(Switch device)
public NvHostSyncpt()
{ {
_counterMin = new int[SyncptsCount]; _device = device;
_counterMax = new int[SyncptsCount]; _counterMin = new int[SynchronizationManager.MaxHardwareSyncpoints];
_counterMax = new int[SynchronizationManager.MaxHardwareSyncpoints];
_waiters = new ConcurrentDictionary<EventWaitHandle, int>(); _clientManaged = new bool[SynchronizationManager.MaxHardwareSyncpoints];
_assigned = new bool[SynchronizationManager.MaxHardwareSyncpoints];
} }
public int GetMin(int id) private void ReserveSyncpointLocked(uint id, bool isClientManaged)
{ {
return _counterMin[id]; if (id >= SynchronizationManager.MaxHardwareSyncpoints || _assigned[id])
}
public int GetMax(int id)
{
return _counterMax[id];
}
public int Increment(int id)
{
if (((_eventMask >> id) & 1) != 0)
{ {
Interlocked.Increment(ref _counterMax[id]); throw new ArgumentOutOfRangeException(nameof(id));
} }
return IncrementMin(id); _assigned[id] = true;
_clientManaged[id] = isClientManaged;
} }
public int IncrementMin(int id) public uint AllocateSyncpoint(bool isClientManaged)
{ {
int value = Interlocked.Increment(ref _counterMin[id]); lock (_syncpointAllocatorLock)
WakeUpWaiters(id, value);
return value;
}
public int IncrementMax(int id)
{
return Interlocked.Increment(ref _counterMax[id]);
}
public void AddWaiter(int threshold, EventWaitHandle waitEvent)
{
if (!_waiters.TryAdd(waitEvent, threshold))
{ {
throw new InvalidOperationException(); for (uint i = 1; i < SynchronizationManager.MaxHardwareSyncpoints; i++)
}
}
public bool RemoveWaiter(EventWaitHandle waitEvent)
{
return _waiters.TryRemove(waitEvent, out _);
}
private void WakeUpWaiters(int id, int newValue)
{
foreach (KeyValuePair<EventWaitHandle, int> kv in _waiters)
{
if (MinCompare(id, newValue, _counterMax[id], kv.Value))
{ {
kv.Key.Set(); if (!_assigned[i])
{
_waiters.TryRemove(kv.Key, out _); ReserveSyncpointLocked(i, isClientManaged);
return i;
}
} }
} }
Logger.PrintError(LogClass.ServiceNv, "Cannot allocate a new syncpoint!");
return 0;
} }
public bool MinCompare(int id, int threshold) public void ReleaseSyncpoint(uint id)
{ {
return MinCompare(id, _counterMin[id], _counterMax[id], threshold); if (id == 0)
{
return;
}
lock (_syncpointAllocatorLock)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints || !_assigned[id])
{
throw new ArgumentOutOfRangeException(nameof(id));
}
_assigned[id] = false;
_clientManaged[id] = false;
SetSyncpointMinEqualSyncpointMax(id);
}
} }
private bool MinCompare(int id, int min, int max, int threshold) public void SetSyncpointMinEqualSyncpointMax(uint id)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
int value = (int)ReadSyncpointValue(id);
Interlocked.Exchange(ref _counterMax[id], value);
}
public uint ReadSyncpointValue(uint id)
{
return UpdateMin(id);
}
public uint ReadSyncpointMinValue(uint id)
{
return (uint)_counterMin[id];
}
public uint ReadSyncpointMaxValue(uint id)
{
return (uint)_counterMax[id];
}
private bool IsClientManaged(uint id)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints)
{
return false;
}
return _clientManaged[id];
}
public void Increment(uint id)
{
if (IsClientManaged(id))
{
IncrementSyncpointMax(id);
}
IncrementSyncpointGPU(id);
}
public uint UpdateMin(uint id)
{
uint newValue = _device.Gpu.Synchronization.GetSyncpointValue(id);
Interlocked.Exchange(ref _counterMin[id], (int)newValue);
return newValue;
}
private void IncrementSyncpointGPU(uint id)
{
_device.Gpu.Synchronization.IncrementSyncpoint(id);
}
public void IncrementSyncpointMin(uint id)
{
Interlocked.Increment(ref _counterMin[id]);
}
public uint IncrementSyncpointMaxExt(uint id, int count)
{
if (count == 0)
{
return ReadSyncpointMaxValue(id);
}
uint result = 0;
for (int i = 0; i < count; i++)
{
result = IncrementSyncpointMax(id);
}
return result;
}
private uint IncrementSyncpointMax(uint id)
{
return (uint)Interlocked.Increment(ref _counterMax[id]);
}
public bool IsSyncpointExpired(uint id, uint threshold)
{
return MinCompare(id, _counterMin[id], _counterMax[id], (int)threshold);
}
private bool MinCompare(uint id, int min, int max, int threshold)
{ {
int minDiff = min - threshold; int minDiff = min - threshold;
int maxDiff = max - threshold; int maxDiff = max - threshold;
if (((_eventMask >> id) & 1) != 0) if (IsClientManaged(id))
{ {
return minDiff >= 0; return minDiff >= 0;
} }

View File

@ -1,12 +1,12 @@
using System.Runtime.InteropServices; using Ryujinx.HLE.HOS.Services.Nv.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
struct SyncptWaitArguments struct SyncptWaitArguments
{ {
public uint Id; public NvFence Fence;
public int Thresh; public int Timeout;
public int Timeout;
} }
} }

View File

@ -6,6 +6,6 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
struct SyncptWaitExArguments struct SyncptWaitExArguments
{ {
public SyncptWaitArguments Input; public SyncptWaitArguments Input;
public int Value; public uint Value;
} }
} }

View File

@ -1,11 +1,36 @@
using System.Runtime.InteropServices; using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.Types namespace Ryujinx.HLE.HOS.Services.Nv.Types
{ {
[StructLayout(LayoutKind.Sequential, Size = 0x8)] [StructLayout(LayoutKind.Sequential, Size = 0x8)]
internal struct NvFence internal struct NvFence
{ {
public const uint InvalidSyncPointId = uint.MaxValue;
public uint Id; public uint Id;
public uint Value; public uint Value;
public bool IsValid()
{
return Id != InvalidSyncPointId;
}
public void UpdateValue(NvHostSyncpt hostSyncpt)
{
Value = hostSyncpt.ReadSyncpointValue(Id);
}
public bool Wait(GpuContext gpuContext, TimeSpan timeout)
{
if (IsValid())
{
return gpuContext.Synchronization.WaitOnSyncpoint(Id, Value, timeout);
}
return false;
}
} }
} }

View File

@ -1,3 +1,4 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
@ -5,7 +6,9 @@ using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -117,15 +120,37 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
private ResultCode GbpDequeueBuffer(ServiceCtx context, BinaryReader parcelReader) private ResultCode GbpDequeueBuffer(ServiceCtx context, BinaryReader parcelReader)
{ {
// TODO: Errors. // TODO: Errors.
int format = parcelReader.ReadInt32(); int async = parcelReader.ReadInt32();
int width = parcelReader.ReadInt32(); int width = parcelReader.ReadInt32();
int height = parcelReader.ReadInt32(); int height = parcelReader.ReadInt32();
int getTimestamps = parcelReader.ReadInt32(); int format = parcelReader.ReadInt32();
int usage = parcelReader.ReadInt32(); int usage = parcelReader.ReadInt32();
int slot = GetFreeSlotBlocking(width, height); int slot = GetFreeSlotBlocking(width, height);
return MakeReplyParcel(context, slot, 1, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); 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) private ResultCode GbpQueueBuffer(ServiceCtx context, BinaryReader parcelReader)
@ -142,9 +167,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
parcelReader.BaseStream.Position = Position; parcelReader.BaseStream.Position = Position;
_bufferQueue[slot].Transform = queueBufferObject.Transform; _bufferQueue[slot].Transform = queueBufferObject.Transform;
_bufferQueue[slot].Fence = queueBufferObject.Fence;
_bufferQueue[slot].Crop = queueBufferObject.Crop; _bufferQueue[slot].Crop = queueBufferObject.Crop;
_bufferQueue[slot].State = BufferState.Queued;
_bufferQueue[slot].State = BufferState.Queued;
SendFrameBuffer(context, slot); SendFrameBuffer(context, slot);
@ -219,14 +244,19 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
return reader.ReadBytes((int)flattenedObjectSize); return reader.ReadBytes((int)flattenedObjectSize);
} }
private unsafe T ReadFlattenedObject<T>(BinaryReader reader) where T: struct private T ReadFlattenedObject<T>(BinaryReader reader) where T: struct
{ {
byte[] data = ReadFlattenedObject(reader); long flattenedObjectSize = reader.ReadInt64();
fixed (byte* ptr = data) Debug.Assert(flattenedObjectSize == Unsafe.SizeOf<T>());
{
return Marshal.PtrToStructure<T>((IntPtr)ptr); 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) private ResultCode MakeReplyParcel(ServiceCtx context, params int[] ints)
@ -328,10 +358,21 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
format, format,
bytesPerPixel, bytesPerPixel,
crop, crop,
AcquireBuffer,
ReleaseBuffer, ReleaseBuffer,
slot); 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) private void ReleaseBuffer(object slot)
{ {
ReleaseBuffer((int)slot); ReleaseBuffer((int)slot);

View File

@ -8,6 +8,8 @@
public Rect Crop; public Rect Crop;
public MultiFence Fence;
public GbpBuffer Data; public GbpBuffer Data;
} }
} }

View File

@ -1,24 +1,49 @@
using Ryujinx.HLE.HOS.Services.Nv.Types; using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{ {
[StructLayout(LayoutKind.Explicit, Size = 0x24)] [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)]
struct MultiFence struct MultiFence
{ {
[FieldOffset(0x0)]
public int FenceCount; public int FenceCount;
[FieldOffset(0x4)] private byte _fenceStorageStart;
public NvFence Fence0;
[FieldOffset(0xC)] private Span<byte> _storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf<NvFence>() * 4);
public NvFence Fence1;
[FieldOffset(0x14)] private Span<NvFence> _nvFences => MemoryMarshal.Cast<byte, NvFence>(_storage);
public NvFence Fence2;
[FieldOffset(0x1C)] public static MultiFence NoFence
public NvFence Fence3; {
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);
}
}
} }
} }

View File

@ -2,7 +2,7 @@
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{ {
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit, Pack = 1)]
struct QueueBufferObject struct QueueBufferObject
{ {
[FieldOffset(0x0)] [FieldOffset(0x0)]