0
0
mirror of https://git.ryujinx.app/ryubing/ryujinx.git synced 2025-04-28 08:25:13 +00:00
Keaton f91cd05260
Implement VP9 loop filtering (#550)
Unmerged PR from OG Ryujinx (#4367). From @gdkchan:

> The main goal of this change is porting the loop filtering from
libvpx, which should fix the block artifacts on some VP9 videos on games
using NVDEC to decode them. In addition to that, there are two other
changes:
> 
> - The remaining decoder code required to decode a VP9 video (with
headers included) has been added. That was done because it's much better
to test the decoder standalone with a video file. I decided to keep that
code on the emulator, even if some of it is unused, since it makes
standalone testing easier in the future too, and we can include unit
tests with video files.
> - Large refactoring of both new and existing code to conform with our
conding [sic] styles, done by @TSRBerry (thanks!) Some of it has been
automated.
> 
> Since we had no loop filtering before, this change will make video
decoding slower. That may cause frame drop etc if the decoder is not
fast enough in some games. I plan to optimize the decoder more in the
future to make up for that, but if possible I'd prefer to not do it as
part of this PR, but if the perf loss is too severe I might consider.
> 
> This will need to be tested on games that had the block artifacts, it
would be nice to confirm if they match hardware now, and get some
before/after screenshots etc.

Comment from @Bjorn29512:

> Significantly improves the block artifacts in FE: Engage.
> 
> Before:
>
![](https://user-images.githubusercontent.com/110204265/216882414-ec88dbda-7544-4490-8a47-37f074056ae3.png)
> 
> After:
>
![](https://user-images.githubusercontent.com/110204265/216882478-4e81fead-1033-4877-b282-f9cac6d6aa3b.png)

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2025-02-18 20:59:36 -06:00

437 lines
18 KiB
C#

using Ryujinx.Graphics.Nvdec.Vp9.Types;
using System.Diagnostics;
namespace Ryujinx.Graphics.Nvdec.Vp9
{
internal static class PredCommon
{
public static int GetReferenceModeContext(ref Vp9Common cm, ref MacroBlockD xd)
{
int ctx;
// Note:
// The mode info data structure has a one element border above and to the
// left of the entries corresponding to real macroblocks.
// The prediction flags in these dummy entries are initialized to 0.
if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull)
{
// both edges available
if (!xd.AboveMi.Value.HasSecondRef() && !xd.LeftMi.Value.HasSecondRef())
{
// Neither edge uses comp pred (0/1)
ctx = (xd.AboveMi.Value.RefFrame[0] == cm.CompFixedRef ? 1 : 0) ^
(xd.LeftMi.Value.RefFrame[0] == cm.CompFixedRef ? 1 : 0);
}
else if (!xd.AboveMi.Value.HasSecondRef())
{
// One of two edges uses comp pred (2/3)
ctx = 2 + (xd.AboveMi.Value.RefFrame[0] == cm.CompFixedRef || !xd.AboveMi.Value.IsInterBlock()
? 1
: 0);
}
else if (!xd.LeftMi.Value.HasSecondRef())
{
// One of two edges uses comp pred (2/3)
ctx = 2 +
(xd.LeftMi.Value.RefFrame[0] == cm.CompFixedRef || !xd.LeftMi.Value.IsInterBlock() ? 1 : 0);
}
else // Both edges use comp pred (4)
{
ctx = 4;
}
}
else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull)
{
// One edge available
ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value;
if (!edgeMi.HasSecondRef())
{
// Edge does not use comp pred (0/1)
ctx = edgeMi.RefFrame[0] == cm.CompFixedRef ? 1 : 0;
}
else
{
// Edge uses comp pred (3)
ctx = 3;
}
}
else
{
// No edges available (1)
ctx = 1;
}
Debug.Assert(ctx >= 0 && ctx < Constants.CompInterContexts);
return ctx;
}
// Returns a context number for the given MB prediction signal
public static int GetPredContextCompRefP(ref Vp9Common cm, ref MacroBlockD xd)
{
int predContext;
// Note:
// The mode info data structure has a one element border above and to the
// left of the entries corresponding to real macroblocks.
// The prediction flags in these dummy entries are initialized to 0.
int fixRefIdx = cm.RefFrameSignBias[cm.CompFixedRef];
int varRefIdx = fixRefIdx == 0 ? 1 : 0;
if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull)
{
// Both edges available
bool aboveIntra = !xd.AboveMi.Value.IsInterBlock();
bool leftIntra = !xd.LeftMi.Value.IsInterBlock();
if (aboveIntra && leftIntra)
{
// Intra/Intra (2)
predContext = 2;
}
else if (aboveIntra || leftIntra)
{
// Intra/Inter
ref ModeInfo edgeMi = ref aboveIntra ? ref xd.LeftMi.Value : ref xd.AboveMi.Value;
if (!edgeMi.HasSecondRef()) // single pred (1/3)
{
predContext = 1 + (2 * (edgeMi.RefFrame[0] != cm.CompVarRef[1] ? 1 : 0));
}
else // Comp pred (1/3)
{
predContext = 1 + (2 * (edgeMi.RefFrame[varRefIdx] != cm.CompVarRef[1] ? 1 : 0));
}
}
else
{
// Inter/Inter
bool lSg = !xd.LeftMi.Value.HasSecondRef();
bool aSg = !xd.AboveMi.Value.HasSecondRef();
sbyte vrfa = aSg ? xd.AboveMi.Value.RefFrame[0] : xd.AboveMi.Value.RefFrame[varRefIdx];
sbyte vrfl = lSg ? xd.LeftMi.Value.RefFrame[0] : xd.LeftMi.Value.RefFrame[varRefIdx];
if (vrfa == vrfl && cm.CompVarRef[1] == vrfa)
{
predContext = 0;
}
else if (lSg && aSg)
{
// Single/Single
if ((vrfa == cm.CompFixedRef && vrfl == cm.CompVarRef[0]) ||
(vrfl == cm.CompFixedRef && vrfa == cm.CompVarRef[0]))
{
predContext = 4;
}
else if (vrfa == vrfl)
{
predContext = 3;
}
else
{
predContext = 1;
}
}
else if (lSg || aSg)
{
// Single/Comp
sbyte vrfc = lSg ? vrfa : vrfl;
sbyte rfs = aSg ? vrfa : vrfl;
if (vrfc == cm.CompVarRef[1] && rfs != cm.CompVarRef[1])
{
predContext = 1;
}
else if (rfs == cm.CompVarRef[1] && vrfc != cm.CompVarRef[1])
{
predContext = 2;
}
else
{
predContext = 4;
}
}
else if (vrfa == vrfl)
{
// Comp/Comp
predContext = 4;
}
else
{
predContext = 2;
}
}
}
else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull)
{
// One edge available
ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value;
if (!edgeMi.IsInterBlock())
{
predContext = 2;
}
else
{
if (edgeMi.HasSecondRef())
{
predContext = 4 * (edgeMi.RefFrame[varRefIdx] != cm.CompVarRef[1] ? 1 : 0);
}
else
{
predContext = 3 * (edgeMi.RefFrame[0] != cm.CompVarRef[1] ? 1 : 0);
}
}
}
else
{
// No edges available (2)
predContext = 2;
}
Debug.Assert(predContext >= 0 && predContext < Constants.RefContexts);
return predContext;
}
public static int GetPredContextSingleRefP1(ref MacroBlockD xd)
{
int predContext;
// Note:
// The mode info data structure has a one element border above and to the
// left of the entries corresponding to real macroblocks.
// The prediction flags in these dummy entries are initialized to 0.
if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull)
{
// Both edges available
bool aboveIntra = !xd.AboveMi.Value.IsInterBlock();
bool leftIntra = !xd.LeftMi.Value.IsInterBlock();
if (aboveIntra && leftIntra)
{
// Intra/Intra
predContext = 2;
}
else if (aboveIntra || leftIntra)
{
// Intra/Inter or Inter/Intra
ref ModeInfo edgeMi = ref aboveIntra ? ref xd.LeftMi.Value : ref xd.AboveMi.Value;
if (!edgeMi.HasSecondRef())
{
predContext = 4 * (edgeMi.RefFrame[0] == Constants.LastFrame ? 1 : 0);
}
else
{
predContext = 1 + (edgeMi.RefFrame[0] == Constants.LastFrame ||
edgeMi.RefFrame[1] == Constants.LastFrame
? 1
: 0);
}
}
else
{
// Inter/Inter
bool aboveHasSecond = xd.AboveMi.Value.HasSecondRef();
bool leftHasSecond = xd.LeftMi.Value.HasSecondRef();
sbyte above0 = xd.AboveMi.Value.RefFrame[0];
sbyte above1 = xd.AboveMi.Value.RefFrame[1];
sbyte left0 = xd.LeftMi.Value.RefFrame[0];
sbyte left1 = xd.LeftMi.Value.RefFrame[1];
if (aboveHasSecond && leftHasSecond)
{
predContext = 1 + (above0 == Constants.LastFrame || above1 == Constants.LastFrame ||
left0 == Constants.LastFrame || left1 == Constants.LastFrame
? 1
: 0);
}
else if (aboveHasSecond || leftHasSecond)
{
sbyte rfs = !aboveHasSecond ? above0 : left0;
sbyte crf1 = aboveHasSecond ? above0 : left0;
sbyte crf2 = aboveHasSecond ? above1 : left1;
if (rfs == Constants.LastFrame)
{
predContext = 3 + (crf1 == Constants.LastFrame || crf2 == Constants.LastFrame ? 1 : 0);
}
else
{
predContext = crf1 == Constants.LastFrame || crf2 == Constants.LastFrame ? 1 : 0;
}
}
else
{
predContext = (2 * (above0 == Constants.LastFrame ? 1 : 0)) +
(2 * (left0 == Constants.LastFrame ? 1 : 0));
}
}
}
else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull)
{
// One edge available
ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value;
if (!edgeMi.IsInterBlock())
{
// Intra
predContext = 2;
}
else
{
// Inter
if (!edgeMi.HasSecondRef())
{
predContext = 4 * (edgeMi.RefFrame[0] == Constants.LastFrame ? 1 : 0);
}
else
{
predContext = 1 + (edgeMi.RefFrame[0] == Constants.LastFrame ||
edgeMi.RefFrame[1] == Constants.LastFrame
? 1
: 0);
}
}
}
else
{
// No edges available
predContext = 2;
}
Debug.Assert(predContext >= 0 && predContext < Constants.RefContexts);
return predContext;
}
public static int GetPredContextSingleRefP2(ref MacroBlockD xd)
{
int predContext;
// Note:
// The mode info data structure has a one element border above and to the
// left of the entries corresponding to real macroblocks.
// The prediction flags in these dummy entries are initialized to 0.
if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull)
{
// Both edges available
bool aboveIntra = !xd.AboveMi.Value.IsInterBlock();
bool leftIntra = !xd.LeftMi.Value.IsInterBlock();
if (aboveIntra && leftIntra)
{
// Intra/Intra
predContext = 2;
}
else if (aboveIntra || leftIntra)
{
// Intra/Inter or Inter/Intra
ref ModeInfo edgeMi = ref aboveIntra ? ref xd.LeftMi.Value : ref xd.AboveMi.Value;
if (!edgeMi.HasSecondRef())
{
if (edgeMi.RefFrame[0] == Constants.LastFrame)
{
predContext = 3;
}
else
{
predContext = 4 * (edgeMi.RefFrame[0] == Constants.GoldenFrame ? 1 : 0);
}
}
else
{
predContext = 1 + (2 * (edgeMi.RefFrame[0] == Constants.GoldenFrame ||
edgeMi.RefFrame[1] == Constants.GoldenFrame
? 1
: 0));
}
}
else
{
// Inter/Inter
bool aboveHasSecond = xd.AboveMi.Value.HasSecondRef();
bool leftHasSecond = xd.LeftMi.Value.HasSecondRef();
sbyte above0 = xd.AboveMi.Value.RefFrame[0];
sbyte above1 = xd.AboveMi.Value.RefFrame[1];
sbyte left0 = xd.LeftMi.Value.RefFrame[0];
sbyte left1 = xd.LeftMi.Value.RefFrame[1];
if (aboveHasSecond && leftHasSecond)
{
if (above0 == left0 && above1 == left1)
{
predContext = 3 * (above0 == Constants.GoldenFrame || above1 == Constants.GoldenFrame ||
left0 == Constants.GoldenFrame || left1 == Constants.GoldenFrame
? 1
: 0);
}
else
{
predContext = 2;
}
}
else if (aboveHasSecond || leftHasSecond)
{
sbyte rfs = !aboveHasSecond ? above0 : left0;
sbyte crf1 = aboveHasSecond ? above0 : left0;
sbyte crf2 = aboveHasSecond ? above1 : left1;
if (rfs == Constants.GoldenFrame)
{
predContext = 3 + (crf1 == Constants.GoldenFrame || crf2 == Constants.GoldenFrame ? 1 : 0);
}
else if (rfs == Constants.AltRefFrame)
{
predContext = crf1 == Constants.GoldenFrame || crf2 == Constants.GoldenFrame ? 1 : 0;
}
else
{
predContext =
1 + (2 * (crf1 == Constants.GoldenFrame || crf2 == Constants.GoldenFrame ? 1 : 0));
}
}
else
{
if (above0 == Constants.LastFrame && left0 == Constants.LastFrame)
{
predContext = 3;
}
else if (above0 == Constants.LastFrame || left0 == Constants.LastFrame)
{
sbyte edge0 = above0 == Constants.LastFrame ? left0 : above0;
predContext = 4 * (edge0 == Constants.GoldenFrame ? 1 : 0);
}
else
{
predContext = (2 * (above0 == Constants.GoldenFrame ? 1 : 0)) +
(2 * (left0 == Constants.GoldenFrame ? 1 : 0));
}
}
}
}
else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull)
{
// One edge available
ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value;
if (!edgeMi.IsInterBlock() || (edgeMi.RefFrame[0] == Constants.LastFrame && !edgeMi.HasSecondRef()))
{
predContext = 2;
}
else if (!edgeMi.HasSecondRef())
{
predContext = 4 * (edgeMi.RefFrame[0] == Constants.GoldenFrame ? 1 : 0);
}
else
{
predContext = 3 * (edgeMi.RefFrame[0] == Constants.GoldenFrame ||
edgeMi.RefFrame[1] == Constants.GoldenFrame
? 1
: 0);
}
}
else
{
// No edges available (2)
predContext = 2;
}
Debug.Assert(predContext >= 0 && predContext < Constants.RefContexts);
return predContext;
}
}
}