mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-07-08 12:09:06 +00:00
VP9-improvements (#132)
- Improves MMNV request handling by tracking active clock requests per session and returning the effective requested rate for each module. - Treats sentinel clock requests (`0` / negative values) as request clears. - Preserves VP9 segment maps across inter frames, resetting them for key/intra/size-change cases. - Avoids stale VP9 motion-vector state by disabling previous MV lookup on key frames and clearing output MVs for intra-only frames. This fixes VP9 playback corruption seen in several Just Dance videos where stale segmentation/MV state could leak across frames and produce blocky/discolored output until a later refresh frame. The MMNV changes also make the service behavior closer to what games expect when requesting and clearing NvDec clocks, removing the stub spam for this service. Co-authored-by: MrKev312 <34964788+MrKev312@users.noreply.github.com> Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/132 Reviewed-by: sh0inx <sh0inx@noreply.git.ryujinx.app>
This commit is contained in:
@@ -11,6 +11,9 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
public bool IsHardwareAccelerated => false;
|
||||
|
||||
private readonly MemoryAllocator _allocator = new();
|
||||
private byte[] _lastSegmentMap = [];
|
||||
private int _lastSegmentMapMiRows;
|
||||
private int _lastSegmentMapMiCols;
|
||||
|
||||
public ISurface CreateSurface(int width, int height)
|
||||
{
|
||||
@@ -106,7 +109,9 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
|
||||
cm.AllocTileWorkerData(_allocator, tileCols, tileRows, maxThreads);
|
||||
cm.AllocContextBuffers(_allocator, output.Width, output.Height);
|
||||
|
||||
cm.InitContextBuffers();
|
||||
LoadSegmentMap(ref cm, ref pictureInfo);
|
||||
cm.SetupSegmentationDequant();
|
||||
cm.SetupScaleFactors();
|
||||
|
||||
@@ -153,6 +158,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
}
|
||||
}
|
||||
|
||||
SaveSegmentMap(ref cm);
|
||||
cm.GetMvs(mvsOut);
|
||||
|
||||
cm.FreeTileWorkerData(_allocator);
|
||||
@@ -161,6 +167,52 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LoadSegmentMap(ref Vp9Common cm, ref Vp9PictureInfo pictureInfo)
|
||||
{
|
||||
int mapLength = cm.MiRows * cm.MiCols;
|
||||
if (mapLength == 0 || cm.LastFrameSegMap.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool reset =
|
||||
pictureInfo.IsKeyFrame ||
|
||||
pictureInfo.IntraOnly ||
|
||||
_lastSegmentMapMiRows != cm.MiRows ||
|
||||
_lastSegmentMapMiCols != cm.MiCols;
|
||||
|
||||
if (_lastSegmentMap.Length != mapLength)
|
||||
{
|
||||
_lastSegmentMap = new byte[mapLength];
|
||||
}
|
||||
else if (reset)
|
||||
{
|
||||
Array.Clear(_lastSegmentMap);
|
||||
}
|
||||
|
||||
_lastSegmentMapMiRows = cm.MiRows;
|
||||
_lastSegmentMapMiCols = cm.MiCols;
|
||||
_lastSegmentMap.CopyTo(cm.LastFrameSegMap.AsSpan());
|
||||
}
|
||||
|
||||
private void SaveSegmentMap(ref Vp9Common cm)
|
||||
{
|
||||
int mapLength = cm.MiRows * cm.MiCols;
|
||||
if (!cm.Seg.Enabled || mapLength == 0 || cm.CurrentFrameSegMap.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastSegmentMap.Length != mapLength)
|
||||
{
|
||||
_lastSegmentMap = new byte[mapLength];
|
||||
}
|
||||
|
||||
cm.CurrentFrameSegMap.AsSpan()[..mapLength].CopyTo(_lastSegmentMap);
|
||||
_lastSegmentMapMiRows = cm.MiRows;
|
||||
_lastSegmentMapMiCols = cm.MiCols;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_allocator.Dispose();
|
||||
|
||||
@@ -1088,6 +1088,12 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
|
||||
|
||||
public void GetMvs(Span<Vp9MvRef> mvs)
|
||||
{
|
||||
if (FrameIsIntraOnly())
|
||||
{
|
||||
mvs.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mvs.Length > CurFrameMvs.Length)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace Ryujinx.Graphics.Nvdec.Types.Vp9
|
||||
IsKeyFrame = Flags.HasFlag(FrameFlags.IsKeyFrame),
|
||||
IntraOnly = Flags.HasFlag(FrameFlags.IntraOnly),
|
||||
UsePrevInFindMvRefs =
|
||||
!Flags.HasFlag(FrameFlags.IsKeyFrame) &&
|
||||
!Flags.HasFlag(FrameFlags.ErrorResilientMode) &&
|
||||
!Flags.HasFlag(FrameFlags.FrameSizeChanged) &&
|
||||
!Flags.HasFlag(FrameFlags.IntraOnly) &&
|
||||
|
||||
@@ -16,10 +16,9 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
public Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent)
|
||||
{
|
||||
bool isAutoClearEvent = autoClearEvent != 0;
|
||||
uint requestId = Register(module, fgmPriority, isAutoClearEvent);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent });
|
||||
|
||||
Register(module, fgmPriority, isAutoClearEvent);
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Initialized module={module} requestId={requestId} autoClearEvent={isAutoClearEvent}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@@ -27,41 +26,48 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
[CmifCommand(1)]
|
||||
public Result FinalizeOld(Module module)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module });
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
_sessionList.Remove(GetSessionByModule(module));
|
||||
Session session = GetSessionByModule(module);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
_sessionList.Remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Finalized module={module}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(2)]
|
||||
public Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax)
|
||||
public Result SetAndWaitOld(Module module, uint clockRateMin, uint timeout)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, clockRateMin, clockRateMax });
|
||||
uint actualClockRate;
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
GetSessionByModule(module)?.SetAndWait(clockRateMin, clockRateMax);
|
||||
Session session = GetSessionByModule(module) ?? RegisterSession(module, false);
|
||||
ApplyClockRequest(session, clockRateMin, timeout);
|
||||
actualClockRate = GetEffectiveClockRate(module);
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"SetAndWait module={module} requested={clockRateMin} actual={actualClockRate} timeout={timeout}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(3)]
|
||||
public Result GetOld(out uint clockRateActual, Module module)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module });
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
Session session = GetSessionByModule(module);
|
||||
|
||||
clockRateActual = session == null ? 0 : session.ClockRateMin;
|
||||
clockRateActual = GetEffectiveClockRate(module);
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"Get module={module} actual={clockRateActual}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@@ -70,51 +76,76 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
{
|
||||
bool isAutoClearEvent = autoClearEvent != 0;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent });
|
||||
|
||||
requestId = Register(module, fgmPriority, isAutoClearEvent);
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Initialized module={module} requestId={requestId} autoClearEvent={isAutoClearEvent}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(5)]
|
||||
public Result Finalize(uint requestId)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId });
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
_sessionList.Remove(GetSessionById(requestId));
|
||||
Session session = GetSessionById(requestId);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
_sessionList.Remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Finalized requestId={requestId}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(6)]
|
||||
public Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax)
|
||||
public Result SetAndWait(uint requestId, uint clockRateMin, uint timeout)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId, clockRateMin, clockRateMax });
|
||||
uint actualClockRate = 0;
|
||||
Module? module = null;
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
GetSessionById(requestId)?.SetAndWait(clockRateMin, clockRateMax);
|
||||
Session session = GetSessionById(requestId);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
module = session.Module;
|
||||
ApplyClockRequest(session, clockRateMin, timeout);
|
||||
actualClockRate = GetEffectiveClockRate(session.Module);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"SetAndWait requestId={requestId} module={module?.ToString() ?? "<missing>"} requested={clockRateMin} actual={actualClockRate} timeout={timeout}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(7)]
|
||||
public Result Get(out uint clockRateActual, uint requestId)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId });
|
||||
Module? module = null;
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
Session session = GetSessionById(requestId);
|
||||
|
||||
clockRateActual = session == null ? 0 : session.ClockRateMin;
|
||||
if (session == null)
|
||||
{
|
||||
clockRateActual = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
module = session.Module;
|
||||
clockRateActual = GetEffectiveClockRate(session.Module);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"Get requestId={requestId} module={module?.ToString() ?? "<missing>"} actual={clockRateActual}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@@ -149,12 +180,51 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
lock (_sessionList)
|
||||
{
|
||||
// Nintendo ignores the fgm priority as the other services were deprecated.
|
||||
Session session = new(_uniqueId++, module, isAutoClearEvent);
|
||||
|
||||
_sessionList.Add(session);
|
||||
Session session = RegisterSession(module, isAutoClearEvent);
|
||||
|
||||
return session.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private Session RegisterSession(Module module, bool isAutoClearEvent)
|
||||
{
|
||||
Session session = new(_uniqueId++, module, isAutoClearEvent);
|
||||
|
||||
_sessionList.Add(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
private void ApplyClockRequest(Session session, uint requestedClockRate, uint timeout)
|
||||
{
|
||||
if (IsClockRateSentinel(requestedClockRate))
|
||||
{
|
||||
session.ClearRequest(timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
session.SetAndWait(requestedClockRate, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private uint GetEffectiveClockRate(Module module)
|
||||
{
|
||||
uint clockRate = 0;
|
||||
|
||||
foreach (Session session in _sessionList)
|
||||
{
|
||||
if (session.Module == module && session.HasActiveRequest && session.RequestedClockRate > clockRate)
|
||||
{
|
||||
clockRate = session.RequestedClockRate;
|
||||
}
|
||||
}
|
||||
|
||||
return clockRate;
|
||||
}
|
||||
|
||||
private static bool IsClockRateSentinel(uint clockRate)
|
||||
{
|
||||
return clockRate == 0 || unchecked((int)clockRate) < 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace Ryujinx.Horizon.Sdk.MmNv
|
||||
{
|
||||
Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent);
|
||||
Result FinalizeOld(Module module);
|
||||
Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax);
|
||||
Result SetAndWaitOld(Module module, uint clockRateMin, uint timeout);
|
||||
Result GetOld(out uint clockRateActual, Module module);
|
||||
Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent);
|
||||
Result Finalize(uint requestId);
|
||||
Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax);
|
||||
Result SetAndWait(uint requestId, uint clockRateMin, uint timeout);
|
||||
Result Get(out uint clockRateActual, uint requestId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,32 @@ namespace Ryujinx.Horizon.Sdk.MmNv
|
||||
public Module Module { get; }
|
||||
public uint Id { get; }
|
||||
public bool IsAutoClearEvent { get; }
|
||||
public uint ClockRateMin { get; private set; }
|
||||
public int ClockRateMax { get; private set; }
|
||||
public uint RequestedClockRate { get; private set; }
|
||||
public uint LastTimeout { get; private set; }
|
||||
public bool HasActiveRequest { get; private set; }
|
||||
|
||||
public Session(uint id, Module module, bool isAutoClearEvent)
|
||||
{
|
||||
Module = module;
|
||||
Id = id;
|
||||
IsAutoClearEvent = isAutoClearEvent;
|
||||
ClockRateMin = 0;
|
||||
ClockRateMax = -1;
|
||||
RequestedClockRate = 0;
|
||||
LastTimeout = uint.MaxValue;
|
||||
HasActiveRequest = false;
|
||||
}
|
||||
|
||||
public void SetAndWait(uint clockRateMin, int clockRateMax)
|
||||
public void SetAndWait(uint clockRate, uint timeout)
|
||||
{
|
||||
ClockRateMin = clockRateMin;
|
||||
ClockRateMax = clockRateMax;
|
||||
RequestedClockRate = clockRate;
|
||||
LastTimeout = timeout;
|
||||
HasActiveRequest = true;
|
||||
}
|
||||
|
||||
public void ClearRequest(uint timeout)
|
||||
{
|
||||
RequestedClockRate = 0;
|
||||
LastTimeout = timeout;
|
||||
HasActiveRequest = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user