aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Vic/Scaler.cs
blob: 18ae66c4a4c0f211ff49bbfcebd22ab2bb27134e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using System;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

namespace Ryujinx.Graphics.Vic
{
    static class Scaler
    {
        public static void DeinterlaceWeave(Span<byte> data, ReadOnlySpan<byte> prevData, int width, int fieldSize, bool isTopField)
        {
            // Prev I    Curr I    Curr P
            // TTTTTTTT  BBBBBBBB  TTTTTTTT
            // --------  --------  BBBBBBBB

            if (isTopField)
            {
                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
                {
                    prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset + fieldSize, width));
                }
            }
            else
            {
                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
                {
                    prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset, width));
                }
            }
        }

        public static void DeinterlaceBob(Span<byte> data, int width, int fieldSize, bool isTopField)
        {
            // Curr I    Curr P
            // TTTTTTTT  TTTTTTTT
            // --------  TTTTTTTT

            if (isTopField)
            {
                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
                {
                    data.Slice(offset, width).CopyTo(data.Slice(offset + fieldSize, width));
                }
            }
            else
            {
                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
                {
                    data.Slice(offset + fieldSize, width).CopyTo(data.Slice(offset, width));
                }
            }
        }

        public unsafe static void DeinterlaceMotionAdaptive(
            Span<byte> data,
            ReadOnlySpan<byte> prevData,
            ReadOnlySpan<byte> nextData,
            int width,
            int fieldSize,
            bool isTopField)
        {
            // Very simple motion adaptive algorithm.
            // If the pixel changed between previous and next frame, use Bob, otherwise use Weave.
            //
            // Example pseudo code:
            // C_even = (P_even == N_even) ? P_even : C_odd
            // Where: C is current frame, P is previous frame and N is next frame, and even/odd are the fields.
            //
            // Note: This does not fully match the hardware algorithm.
            // The motion adaptive deinterlacing implemented on hardware is considerably more complex,
            // and hard to implement accurately without proper documentation as for example, the
            // method used for motion estimation is unknown.

            int start = isTopField ? fieldSize : 0;
            int otherFieldOffset = isTopField ? -fieldSize : fieldSize;

            fixed (byte* pData = data, pPrevData = prevData, pNextData = nextData)
            {
                for (int offset = start; offset < data.Length; offset += fieldSize * 2)
                {
                    int refOffset = (offset - start) >> 1;
                    int x = 0;

                    if (Avx2.IsSupported)
                    {
                        for (; x < (width & ~0x1f); x += 32)
                        {
                            Vector256<byte> prevPixels = Avx.LoadVector256(pPrevData + refOffset + x);
                            Vector256<byte> nextPixels = Avx.LoadVector256(pNextData + refOffset + x);
                            Vector256<byte> bob = Avx.LoadVector256(pData + offset + otherFieldOffset + x);
                            Vector256<byte> diff = Avx2.CompareEqual(prevPixels, nextPixels);
                            Avx.Store(pData + offset + x, Avx2.BlendVariable(bob, prevPixels, diff));
                        }
                    }
                    else if (Sse41.IsSupported)
                    {
                        for (; x < (width & ~0xf); x += 16)
                        {
                            Vector128<byte> prevPixels = Sse2.LoadVector128(pPrevData + refOffset + x);
                            Vector128<byte> nextPixels = Sse2.LoadVector128(pNextData + refOffset + x);
                            Vector128<byte> bob = Sse2.LoadVector128(pData + offset + otherFieldOffset + x);
                            Vector128<byte> diff = Sse2.CompareEqual(prevPixels, nextPixels);
                            Sse2.Store(pData + offset + x, Sse41.BlendVariable(bob, prevPixels, diff));
                        }
                    }

                    for (; x < width; x++)
                    {
                        byte prevPixel = prevData[refOffset + x];
                        byte nextPixel = nextData[refOffset + x];

                        if (nextPixel != prevPixel)
                        {
                            data[offset + x] = data[offset + otherFieldOffset + x];
                        }
                        else
                        {
                            data[offset + x] = prevPixel;
                        }
                    }
                }
            }
        }
    }
}