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;
|
public bool IsHardwareAccelerated => false;
|
||||||
|
|
||||||
private readonly MemoryAllocator _allocator = new();
|
private readonly MemoryAllocator _allocator = new();
|
||||||
|
private byte[] _lastSegmentMap = [];
|
||||||
|
private int _lastSegmentMapMiRows;
|
||||||
|
private int _lastSegmentMapMiCols;
|
||||||
|
|
||||||
public ISurface CreateSurface(int width, int height)
|
public ISurface CreateSurface(int width, int height)
|
||||||
{
|
{
|
||||||
@@ -106,7 +109,9 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
|||||||
|
|
||||||
cm.AllocTileWorkerData(_allocator, tileCols, tileRows, maxThreads);
|
cm.AllocTileWorkerData(_allocator, tileCols, tileRows, maxThreads);
|
||||||
cm.AllocContextBuffers(_allocator, output.Width, output.Height);
|
cm.AllocContextBuffers(_allocator, output.Width, output.Height);
|
||||||
|
|
||||||
cm.InitContextBuffers();
|
cm.InitContextBuffers();
|
||||||
|
LoadSegmentMap(ref cm, ref pictureInfo);
|
||||||
cm.SetupSegmentationDequant();
|
cm.SetupSegmentationDequant();
|
||||||
cm.SetupScaleFactors();
|
cm.SetupScaleFactors();
|
||||||
|
|
||||||
@@ -153,6 +158,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SaveSegmentMap(ref cm);
|
||||||
cm.GetMvs(mvsOut);
|
cm.GetMvs(mvsOut);
|
||||||
|
|
||||||
cm.FreeTileWorkerData(_allocator);
|
cm.FreeTileWorkerData(_allocator);
|
||||||
@@ -161,6 +167,52 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
|||||||
return true;
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_allocator.Dispose();
|
_allocator.Dispose();
|
||||||
|
|||||||
@@ -1088,6 +1088,12 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
|
|||||||
|
|
||||||
public void GetMvs(Span<Vp9MvRef> mvs)
|
public void GetMvs(Span<Vp9MvRef> mvs)
|
||||||
{
|
{
|
||||||
|
if (FrameIsIntraOnly())
|
||||||
|
{
|
||||||
|
mvs.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mvs.Length > CurFrameMvs.Length)
|
if (mvs.Length > CurFrameMvs.Length)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
throw new ArgumentException(
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ namespace Ryujinx.Graphics.Nvdec.Types.Vp9
|
|||||||
IsKeyFrame = Flags.HasFlag(FrameFlags.IsKeyFrame),
|
IsKeyFrame = Flags.HasFlag(FrameFlags.IsKeyFrame),
|
||||||
IntraOnly = Flags.HasFlag(FrameFlags.IntraOnly),
|
IntraOnly = Flags.HasFlag(FrameFlags.IntraOnly),
|
||||||
UsePrevInFindMvRefs =
|
UsePrevInFindMvRefs =
|
||||||
|
!Flags.HasFlag(FrameFlags.IsKeyFrame) &&
|
||||||
!Flags.HasFlag(FrameFlags.ErrorResilientMode) &&
|
!Flags.HasFlag(FrameFlags.ErrorResilientMode) &&
|
||||||
!Flags.HasFlag(FrameFlags.FrameSizeChanged) &&
|
!Flags.HasFlag(FrameFlags.FrameSizeChanged) &&
|
||||||
!Flags.HasFlag(FrameFlags.IntraOnly) &&
|
!Flags.HasFlag(FrameFlags.IntraOnly) &&
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
|||||||
public Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent)
|
public Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent)
|
||||||
{
|
{
|
||||||
bool isAutoClearEvent = autoClearEvent != 0;
|
bool isAutoClearEvent = autoClearEvent != 0;
|
||||||
|
uint requestId = Register(module, fgmPriority, isAutoClearEvent);
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent });
|
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Initialized module={module} requestId={requestId} autoClearEvent={isAutoClearEvent}");
|
||||||
|
|
||||||
Register(module, fgmPriority, isAutoClearEvent);
|
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
@@ -27,41 +26,48 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
|||||||
[CmifCommand(1)]
|
[CmifCommand(1)]
|
||||||
public Result FinalizeOld(Module module)
|
public Result FinalizeOld(Module module)
|
||||||
{
|
{
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module });
|
|
||||||
|
|
||||||
lock (_sessionList)
|
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;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CmifCommand(2)]
|
[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)
|
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;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CmifCommand(3)]
|
[CmifCommand(3)]
|
||||||
public Result GetOld(out uint clockRateActual, Module module)
|
public Result GetOld(out uint clockRateActual, Module module)
|
||||||
{
|
{
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module });
|
|
||||||
|
|
||||||
lock (_sessionList)
|
lock (_sessionList)
|
||||||
{
|
{
|
||||||
Session session = GetSessionByModule(module);
|
clockRateActual = GetEffectiveClockRate(module);
|
||||||
|
|
||||||
clockRateActual = session == null ? 0 : session.ClockRateMin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"Get module={module} actual={clockRateActual}");
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,51 +76,76 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
|||||||
{
|
{
|
||||||
bool isAutoClearEvent = autoClearEvent != 0;
|
bool isAutoClearEvent = autoClearEvent != 0;
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent });
|
|
||||||
|
|
||||||
requestId = Register(module, fgmPriority, isAutoClearEvent);
|
requestId = Register(module, fgmPriority, isAutoClearEvent);
|
||||||
|
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Initialized module={module} requestId={requestId} autoClearEvent={isAutoClearEvent}");
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CmifCommand(5)]
|
[CmifCommand(5)]
|
||||||
public Result Finalize(uint requestId)
|
public Result Finalize(uint requestId)
|
||||||
{
|
{
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId });
|
|
||||||
|
|
||||||
lock (_sessionList)
|
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;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CmifCommand(6)]
|
[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)
|
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;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CmifCommand(7)]
|
[CmifCommand(7)]
|
||||||
public Result Get(out uint clockRateActual, uint requestId)
|
public Result Get(out uint clockRateActual, uint requestId)
|
||||||
{
|
{
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId });
|
Module? module = null;
|
||||||
|
|
||||||
lock (_sessionList)
|
lock (_sessionList)
|
||||||
{
|
{
|
||||||
Session session = GetSessionById(requestId);
|
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;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,12 +180,51 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
|||||||
lock (_sessionList)
|
lock (_sessionList)
|
||||||
{
|
{
|
||||||
// Nintendo ignores the fgm priority as the other services were deprecated.
|
// Nintendo ignores the fgm priority as the other services were deprecated.
|
||||||
Session session = new(_uniqueId++, module, isAutoClearEvent);
|
Session session = RegisterSession(module, isAutoClearEvent);
|
||||||
|
|
||||||
_sessionList.Add(session);
|
|
||||||
|
|
||||||
return session.Id;
|
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 InitializeOld(Module module, uint fgmPriority, uint autoClearEvent);
|
||||||
Result FinalizeOld(Module module);
|
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 GetOld(out uint clockRateActual, Module module);
|
||||||
Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent);
|
Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent);
|
||||||
Result Finalize(uint requestId);
|
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);
|
Result Get(out uint clockRateActual, uint requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,32 @@ namespace Ryujinx.Horizon.Sdk.MmNv
|
|||||||
public Module Module { get; }
|
public Module Module { get; }
|
||||||
public uint Id { get; }
|
public uint Id { get; }
|
||||||
public bool IsAutoClearEvent { get; }
|
public bool IsAutoClearEvent { get; }
|
||||||
public uint ClockRateMin { get; private set; }
|
public uint RequestedClockRate { get; private set; }
|
||||||
public int ClockRateMax { get; private set; }
|
public uint LastTimeout { get; private set; }
|
||||||
|
public bool HasActiveRequest { get; private set; }
|
||||||
|
|
||||||
public Session(uint id, Module module, bool isAutoClearEvent)
|
public Session(uint id, Module module, bool isAutoClearEvent)
|
||||||
{
|
{
|
||||||
Module = module;
|
Module = module;
|
||||||
Id = id;
|
Id = id;
|
||||||
IsAutoClearEvent = isAutoClearEvent;
|
IsAutoClearEvent = isAutoClearEvent;
|
||||||
ClockRateMin = 0;
|
RequestedClockRate = 0;
|
||||||
ClockRateMax = -1;
|
LastTimeout = uint.MaxValue;
|
||||||
|
HasActiveRequest = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAndWait(uint clockRateMin, int clockRateMax)
|
public void SetAndWait(uint clockRate, uint timeout)
|
||||||
{
|
{
|
||||||
ClockRateMin = clockRateMin;
|
RequestedClockRate = clockRate;
|
||||||
ClockRateMax = clockRateMax;
|
LastTimeout = timeout;
|
||||||
|
HasActiveRequest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearRequest(uint timeout)
|
||||||
|
{
|
||||||
|
RequestedClockRate = 0;
|
||||||
|
LastTimeout = timeout;
|
||||||
|
HasActiveRequest = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user