From d1604aa762a3f669a3fecff0a30b7360399954bc Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Tue, 12 Oct 2021 22:55:57 +0200
Subject: [PATCH] nvdec: Adding Vp8 codec support (#2707)

* first try

* second try

* working update

* Final impl

* Fixes nits

* Fix everything

* remove leftover

* Update FFmpegContext.cs

* Update Surface.cs

* Addresses gdkchan feedback

* bool not byte

* Addresses gdkchan feedback
---
 .../FFmpegContext.cs                          | 16 ++--
 .../H264}/Decoder.cs                          |  9 ++-
 .../H264}/H264BitStreamWriter.cs              |  2 +-
 .../H264}/SpsAndPpsReconstruction.cs          |  2 +-
 .../Ryujinx.Graphics.Nvdec.FFmpeg.csproj      |  5 +-
 .../Surface.cs                                |  2 +-
 Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs  | 53 +++++++++++++
 Ryujinx.Graphics.Nvdec/H264Decoder.cs         |  6 +-
 Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs | 20 +++--
 Ryujinx.Graphics.Nvdec/NvdecDevice.cs         |  3 +
 .../Ryujinx.Graphics.Nvdec.csproj             |  2 +-
 .../Types/Vp8/PictureInfo.cs                  | 75 +++++++++++++++++++
 Ryujinx.Graphics.Nvdec/Vp8Decoder.cs          | 33 ++++++++
 Ryujinx.Graphics.Video/Vp8PictureInfo.cs      | 11 +++
 Ryujinx.sln                                   | 14 ++--
 Ryujinx/Ryujinx.csproj                        |  2 +-
 16 files changed, 220 insertions(+), 35 deletions(-)
 rename {Ryujinx.Graphics.Nvdec.H264 => Ryujinx.Graphics.Nvdec.FFmpeg}/FFmpegContext.cs (90%)
 rename {Ryujinx.Graphics.Nvdec.H264 => Ryujinx.Graphics.Nvdec.FFmpeg/H264}/Decoder.cs (84%)
 rename {Ryujinx.Graphics.Nvdec.H264 => Ryujinx.Graphics.Nvdec.FFmpeg/H264}/H264BitStreamWriter.cs (98%)
 rename {Ryujinx.Graphics.Nvdec.H264 => Ryujinx.Graphics.Nvdec.FFmpeg/H264}/SpsAndPpsReconstruction.cs (99%)
 rename Ryujinx.Graphics.Nvdec.H264/Ryujinx.Graphics.Nvdec.H264.csproj => Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj (63%)
 rename {Ryujinx.Graphics.Nvdec.H264 => Ryujinx.Graphics.Nvdec.FFmpeg}/Surface.cs (96%)
 create mode 100644 Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs
 create mode 100644 Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs
 create mode 100644 Ryujinx.Graphics.Nvdec/Vp8Decoder.cs
 create mode 100644 Ryujinx.Graphics.Video/Vp8PictureInfo.cs

diff --git a/Ryujinx.Graphics.Nvdec.H264/FFmpegContext.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs
similarity index 90%
rename from Ryujinx.Graphics.Nvdec.H264/FFmpegContext.cs
rename to Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs
index 66b1e6c1aa..1e3b88bbfd 100644
--- a/Ryujinx.Graphics.Nvdec.H264/FFmpegContext.cs
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs
@@ -5,27 +5,26 @@ using System.Diagnostics;
 using System.IO;
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.Graphics.Nvdec.H264
+namespace Ryujinx.Graphics.Nvdec.FFmpeg
 {
     unsafe class FFmpegContext : IDisposable
     {
-        private readonly AVCodec_decode _h264Decode;
+        private readonly AVCodec_decode _decodeFrame;
         private static readonly av_log_set_callback_callback _logFunc;
         private readonly AVCodec* _codec;
         private AVPacket* _packet;
         private AVCodecContext* _context;
 
-        public FFmpegContext()
+        public FFmpegContext(AVCodecID codecId)
         {
-            _codec = ffmpeg.avcodec_find_decoder(AVCodecID.AV_CODEC_ID_H264);
+            _codec = ffmpeg.avcodec_find_decoder(codecId);
             _context = ffmpeg.avcodec_alloc_context3(_codec);
-            _context->debug |= ffmpeg.FF_DEBUG_MMCO;
 
             ffmpeg.avcodec_open2(_context, _codec, null);
 
             _packet = ffmpeg.av_packet_alloc();
 
-            _h264Decode = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(_codec->decode.Pointer);
+            _decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(_codec->decode.Pointer);
         }
 
         static FFmpegContext()
@@ -115,7 +114,7 @@ namespace Ryujinx.Graphics.Nvdec.H264
             {
                 _packet->data = ptr;
                 _packet->size = bitstream.Length;
-                result = _h264Decode(_context, output.Frame, &gotFrame, _packet);
+                result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
             }
 
             if (gotFrame == 0)
@@ -126,7 +125,7 @@ namespace Ryujinx.Graphics.Nvdec.H264
                 // Get the next delayed frame by passing a 0 length packet.
                 _packet->data = null;
                 _packet->size = 0;
-                result = _h264Decode(_context, output.Frame, &gotFrame, _packet);
+                result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
 
                 // We need to set B frames to 0 as we already consumed all delayed frames.
                 // This prevents the decoder from trying to return a delayed frame next time.
@@ -138,6 +137,7 @@ namespace Ryujinx.Graphics.Nvdec.H264
             if (gotFrame == 0)
             {
                 ffmpeg.av_frame_unref(output.Frame);
+
                 return -1;
             }
 
diff --git a/Ryujinx.Graphics.Nvdec.H264/Decoder.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs
similarity index 84%
rename from Ryujinx.Graphics.Nvdec.H264/Decoder.cs
rename to Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs
index fed64af4f3..8deda42a5e 100644
--- a/Ryujinx.Graphics.Nvdec.H264/Decoder.cs
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs
@@ -1,7 +1,8 @@
-using Ryujinx.Graphics.Video;
+using FFmpeg.AutoGen;
+using Ryujinx.Graphics.Video;
 using System;
 
-namespace Ryujinx.Graphics.Nvdec.H264
+namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
 {
     public sealed class Decoder : IH264Decoder
     {
@@ -11,7 +12,7 @@ namespace Ryujinx.Graphics.Nvdec.H264
 
         private readonly byte[] _workBuffer = new byte[WorkBufferSize];
 
-        private FFmpegContext _context = new FFmpegContext();
+        private FFmpegContext _context = new FFmpegContext(AVCodecID.AV_CODEC_ID_H264);
 
         private int _oldOutputWidth;
         private int _oldOutputHeight;
@@ -29,7 +30,7 @@ namespace Ryujinx.Graphics.Nvdec.H264
                 outSurf.RequestedHeight != _oldOutputHeight)
             {
                 _context.Dispose();
-                _context = new FFmpegContext();
+                _context = new FFmpegContext(AVCodecID.AV_CODEC_ID_H264);
 
                 _oldOutputWidth = outSurf.RequestedWidth;
                 _oldOutputHeight = outSurf.RequestedHeight;
diff --git a/Ryujinx.Graphics.Nvdec.H264/H264BitStreamWriter.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs
similarity index 98%
rename from Ryujinx.Graphics.Nvdec.H264/H264BitStreamWriter.cs
rename to Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs
index c0e2357de6..3d3b32933b 100644
--- a/Ryujinx.Graphics.Nvdec.H264/H264BitStreamWriter.cs
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Numerics;
 
-namespace Ryujinx.Graphics.Nvdec.H264
+namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
 {
     struct H264BitStreamWriter
     {
diff --git a/Ryujinx.Graphics.Nvdec.H264/SpsAndPpsReconstruction.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs
similarity index 99%
rename from Ryujinx.Graphics.Nvdec.H264/SpsAndPpsReconstruction.cs
rename to Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs
index 6fd1ce79fd..5c16ef3df2 100644
--- a/Ryujinx.Graphics.Nvdec.H264/SpsAndPpsReconstruction.cs
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs
@@ -2,7 +2,7 @@
 using Ryujinx.Graphics.Video;
 using System;
 
-namespace Ryujinx.Graphics.Nvdec.H264
+namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
 {
     static class SpsAndPpsReconstruction
     {
diff --git a/Ryujinx.Graphics.Nvdec.H264/Ryujinx.Graphics.Nvdec.H264.csproj b/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
similarity index 63%
rename from Ryujinx.Graphics.Nvdec.H264/Ryujinx.Graphics.Nvdec.H264.csproj
rename to Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
index fdcdae06de..b437f36e69 100644
--- a/Ryujinx.Graphics.Nvdec.H264/Ryujinx.Graphics.Nvdec.H264.csproj
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>net5.0</TargetFramework>
@@ -6,10 +6,11 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="FFmpeg.AutoGen" Version="4.4.0" />
+    <PackageReference Include="FFmpeg.AutoGen" Version="4.4.1" />
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj" />
   </ItemGroup>
 
diff --git a/Ryujinx.Graphics.Nvdec.H264/Surface.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs
similarity index 96%
rename from Ryujinx.Graphics.Nvdec.H264/Surface.cs
rename to Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs
index 3dbc980e36..20cee4a180 100644
--- a/Ryujinx.Graphics.Nvdec.H264/Surface.cs
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs
@@ -2,7 +2,7 @@
 using Ryujinx.Graphics.Video;
 using System;
 
-namespace Ryujinx.Graphics.Nvdec.H264
+namespace Ryujinx.Graphics.Nvdec.FFmpeg
 {
     unsafe class Surface : ISurface
     {
diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs
new file mode 100644
index 0000000000..f12de2875b
--- /dev/null
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs
@@ -0,0 +1,53 @@
+using FFmpeg.AutoGen;
+using Ryujinx.Graphics.Video;
+using System;
+
+namespace Ryujinx.Graphics.Nvdec.FFmpeg.Vp8
+{
+    public sealed class Decoder : IDecoder
+    {
+        public bool IsHardwareAccelerated => false;
+
+        private readonly FFmpegContext _context = new FFmpegContext(AVCodecID.AV_CODEC_ID_VP8);
+
+        public ISurface CreateSurface(int width, int height)
+        {
+            return new Surface(width, height);
+        }
+
+        public bool Decode(ref Vp8PictureInfo pictureInfo, ISurface output, ReadOnlySpan<byte> bitstream)
+        {
+            Surface outSurf = (Surface)output;
+
+            int uncompHeaderSize = pictureInfo.KeyFrame ? 10 : 3;
+
+            byte[] frame = new byte[bitstream.Length + uncompHeaderSize];
+
+            uint firstPartSizeShifted = pictureInfo.FirstPartSize << 5;
+
+            frame[0] = (byte)(pictureInfo.KeyFrame ? 0 : 1);
+            frame[0] |= (byte)((pictureInfo.Version & 7) << 1);
+            frame[0] |= 1 << 4;
+            frame[0] |= (byte)firstPartSizeShifted;
+            frame[1] |= (byte)(firstPartSizeShifted >> 8);
+            frame[2] |= (byte)(firstPartSizeShifted >> 16);
+
+            if (pictureInfo.KeyFrame)
+            {
+                frame[3] = 0x9d;
+                frame[4] = 0x01;
+                frame[5] = 0x2a;
+                frame[6] = (byte)pictureInfo.FrameWidth;
+                frame[7] = (byte)((pictureInfo.FrameWidth >> 8) & 0x3F);
+                frame[8] = (byte)pictureInfo.FrameHeight;
+                frame[9] = (byte)((pictureInfo.FrameHeight >> 8) & 0x3F);
+            }
+
+            bitstream.CopyTo(new Span<byte>(frame).Slice(uncompHeaderSize));
+
+            return _context.DecodeFrame(outSurf, frame) == 0;
+        }
+
+        public void Dispose() => _context.Dispose();
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Nvdec/H264Decoder.cs b/Ryujinx.Graphics.Nvdec/H264Decoder.cs
index 1ee3997b56..69eeb4949d 100644
--- a/Ryujinx.Graphics.Nvdec/H264Decoder.cs
+++ b/Ryujinx.Graphics.Nvdec/H264Decoder.cs
@@ -1,4 +1,4 @@
-using Ryujinx.Graphics.Nvdec.H264;
+using Ryujinx.Graphics.Nvdec.FFmpeg.H264;
 using Ryujinx.Graphics.Nvdec.Image;
 using Ryujinx.Graphics.Nvdec.Types.H264;
 using Ryujinx.Graphics.Video;
@@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Nvdec
     {
         private const int MbSizeInPixels = 16;
 
-        public unsafe static void Decode(NvdecDecoderContext context, ResourceManager rm, ref NvdecRegisters state)
+        public static void Decode(NvdecDecoderContext context, ResourceManager rm, ref NvdecRegisters state)
         {
             PictureInfo pictureInfo = rm.Gmm.DeviceRead<PictureInfo>(state.SetPictureInfoOffset);
             H264PictureInfo info = pictureInfo.Convert();
@@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.Nvdec
             uint lumaOffset   = state.SetSurfaceLumaOffset[surfaceIndex];
             uint chromaOffset = state.SetSurfaceChromaOffset[surfaceIndex];
 
-            Decoder decoder = context.GetDecoder();
+            Decoder decoder = context.GetH264Decoder();
 
             ISurface outputSurface = rm.Cache.Get(decoder, 0, 0, width, height);
 
diff --git a/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs b/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs
index 90da0bee74..54934bc5ac 100644
--- a/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs
+++ b/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs
@@ -1,21 +1,29 @@
-using Ryujinx.Graphics.Nvdec.H264;
 using System;
 
 namespace Ryujinx.Graphics.Nvdec
 {
     class NvdecDecoderContext : IDisposable
     {
-        private Decoder _decoder;
+        private FFmpeg.H264.Decoder _h264Decoder;
+        private FFmpeg.Vp8.Decoder _vp8Decoder;
 
-        public Decoder GetDecoder()
+        public FFmpeg.H264.Decoder GetH264Decoder()
         {
-            return _decoder ??= new Decoder();
+            return _h264Decoder ??= new FFmpeg.H264.Decoder();
+        }
+
+        public FFmpeg.Vp8.Decoder GetVp8Decoder()
+        {
+            return _vp8Decoder ??= new FFmpeg.Vp8.Decoder();
         }
 
         public void Dispose()
         {
-            _decoder?.Dispose();
-            _decoder = null;
+            _h264Decoder?.Dispose();
+            _h264Decoder = null;
+
+            _vp8Decoder?.Dispose();
+            _vp8Decoder = null;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Nvdec/NvdecDevice.cs b/Ryujinx.Graphics.Nvdec/NvdecDevice.cs
index 5319429bb9..18c2fc130a 100644
--- a/Ryujinx.Graphics.Nvdec/NvdecDevice.cs
+++ b/Ryujinx.Graphics.Nvdec/NvdecDevice.cs
@@ -68,6 +68,9 @@ namespace Ryujinx.Graphics.Nvdec
                 case CodecId.H264:
                     H264Decoder.Decode(_currentContext, _rm, ref _state.State);
                     break;
+                case CodecId.Vp8:
+                    Vp8Decoder.Decode(_currentContext, _rm, ref _state.State);
+                    break;
                 case CodecId.Vp9:
                     Vp9Decoder.Decode(_rm, ref _state.State);
                     break;
diff --git a/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj b/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
index 4c20979ddb..095e0e5995 100644
--- a/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
+++ b/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
@@ -9,7 +9,7 @@
     <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
-    <ProjectReference Include="..\Ryujinx.Graphics.Nvdec.H264\Ryujinx.Graphics.Nvdec.H264.csproj" />
+    <ProjectReference Include="..\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Nvdec.Vp9\Ryujinx.Graphics.Nvdec.Vp9.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj" />
diff --git a/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs b/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs
new file mode 100644
index 0000000000..844f21030d
--- /dev/null
+++ b/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs
@@ -0,0 +1,75 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.Video;
+
+namespace Ryujinx.Graphics.Nvdec.Types.Vp8
+{
+    struct PictureInfo
+    {
+#pragma warning disable CS0649
+        public Array13<uint> Unknown0;
+        public uint GpTimerTimeoutValue;
+        public ushort FrameWidth;
+        public ushort FrameHeight;
+        public byte KeyFrame; // 1: key frame - 0: not
+        public byte Version;
+        public byte Flags0;
+        // TileFormat : 2 // 0: TBL; 1: KBL;
+        // GobHeight : 3 // Set GOB height, 0: GOB_2, 1: GOB_4, 2: GOB_8, 3: GOB_16, 4: GOB_32 (NVDEC3 onwards)
+        // ReserverdSurfaceFormat : 3
+        public byte ErrorConcealOn; // 1: error conceal on - 0: off
+        public uint FirstPartSize; // the size of first partition (frame header and mb header partition)
+        public uint HistBufferSize; // in units of 256
+        public uint VLDBufferSize; // in units of 1
+        public Array2<uint> FrameStride; // [y_c]
+        public uint LumaTopOffset; // offset of luma top field in units of 256
+        public uint LumaBotOffset; // offset of luma bottom field in units of 256
+        public uint LumaFrameOffset; // offset of luma frame in units of 256
+        public uint ChromaTopOffset; // offset of chroma top field in units of 256
+        public uint ChromaBotOffset; // offset of chroma bottom field in units of 256
+        public uint ChromaFrameOffset; // offset of chroma frame in units of 256
+        public uint Flags1;
+        // EnableTFOutput : 1; // =1, enable dbfdma to output the display surface; if disable, then the following configure on tf is useless.
+        // Remap for VC1
+        // VC1MapYFlag : 1
+        // MapYValue : 3
+        // VC1MapUVFlag : 1
+        // MapUVValue : 3
+        // TF
+        // OutStride : 8
+        // TilingFormat : 3;
+        // OutputStructure : 1 // 0:frame, 1:field
+        // Reserved0 : 11
+        public Array2<int> OutputTop; // in units of 256
+        public Array2<int> OutputBottom; // in units of 256
+        // Histogram
+        public uint Flags2;
+        // EnableHistogram : 1 // enable histogram info collection
+        // HistogramStartX : 12 // start X of Histogram window
+        // HistogramStartY : 12 // start Y of Histogram window
+        // Reserved1 : 7
+        // HistogramEndX : 12 // end X of Histogram window
+        // HistogramEndY : 12 // end y of Histogram window
+        // Reserved2 : 8
+        // Decode picture buffer related
+        public sbyte CurrentOutputMemoryLayout;
+        public Array3<sbyte> OutputMemoryLayout; // output NV12/NV24 setting. item 0:golden - 1: altref - 2: last
+        public byte SegmentationFeatureDataUpdate;
+        public Array3<byte> Reserved3;
+        public uint ResultValue; // ucode return result
+        public Array8<uint> PartitionOffset;
+        public Array3<uint> Reserved4;
+#pragma warning restore CS0649
+
+        public Vp8PictureInfo Convert()
+        {
+            return new Vp8PictureInfo()
+            {
+                KeyFrame = KeyFrame != 0,
+                FirstPartSize = FirstPartSize,
+                Version = Version,
+                FrameWidth = FrameWidth,
+                FrameHeight = FrameHeight
+            };
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs b/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs
new file mode 100644
index 0000000000..8a369984ea
--- /dev/null
+++ b/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Graphics.Nvdec.FFmpeg.Vp8;
+using Ryujinx.Graphics.Nvdec.Image;
+using Ryujinx.Graphics.Nvdec.Types.Vp8;
+using Ryujinx.Graphics.Video;
+using System;
+
+namespace Ryujinx.Graphics.Nvdec
+{
+    static class Vp8Decoder
+    {
+        public static void Decode(NvdecDecoderContext context, ResourceManager rm, ref NvdecRegisters state)
+        {
+            PictureInfo pictureInfo = rm.Gmm.DeviceRead<PictureInfo>(state.SetPictureInfoOffset);
+            ReadOnlySpan<byte> bitstream = rm.Gmm.DeviceGetSpan(state.SetBitstreamOffset, (int)pictureInfo.VLDBufferSize);
+
+            Decoder decoder = context.GetVp8Decoder();
+
+            ISurface outputSurface = rm.Cache.Get(decoder, 0, 0, pictureInfo.FrameWidth, pictureInfo.FrameHeight);
+
+            Vp8PictureInfo info = pictureInfo.Convert();
+
+            uint lumaOffset = state.SetSurfaceLumaOffset[3];
+            uint chromaOffset = state.SetSurfaceChromaOffset[3];
+
+            if (decoder.Decode(ref info, outputSurface, bitstream))
+            {
+                SurfaceWriter.Write(rm.Gmm, outputSurface, lumaOffset, chromaOffset);
+            }
+
+            rm.Cache.Put(outputSurface);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Video/Vp8PictureInfo.cs b/Ryujinx.Graphics.Video/Vp8PictureInfo.cs
new file mode 100644
index 0000000000..878674b8e2
--- /dev/null
+++ b/Ryujinx.Graphics.Video/Vp8PictureInfo.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Video
+{
+    public ref struct Vp8PictureInfo
+    {
+        public bool KeyFrame;
+        public uint FirstPartSize;
+        public uint Version;
+        public ushort FrameWidth;
+        public ushort FrameHeight;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.sln b/Ryujinx.sln
index 9504bbc2db..e0d35bdb5f 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -51,8 +51,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.Vp9"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj", "{81BB2C11-9408-4EA3-822E-42987AF54429}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.H264", "Ryujinx.Graphics.Nvdec.H264\Ryujinx.Graphics.Nvdec.H264.csproj", "{990F9601-343E-46CB-8529-B498FA761A92}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}"
@@ -67,7 +65,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "Ryuj
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Headless.SDL2", "Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -159,10 +159,6 @@ Global
 		{81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.Build.0 = Release|Any CPU
-		{990F9601-343E-46CB-8529-B498FA761A92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{990F9601-343E-46CB-8529-B498FA761A92}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{990F9601-343E-46CB-8529-B498FA761A92}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{990F9601-343E-46CB-8529-B498FA761A92}.Release|Any CPU.Build.0 = Release|Any CPU
 		{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -195,6 +191,10 @@ Global
 		{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index 86afe71e23..aba9b53c5b 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -20,7 +20,7 @@
     <PackageReference Include="DiscordRichPresence" Version="1.0.175" />
     <PackageReference Include="GtkSharp" Version="3.22.25.128" />
     <PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
-    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build7" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
+    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
     <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
     <PackageReference Include="OpenTK.Graphics" Version="4.5.0" />
     <PackageReference Include="SPB" Version="0.0.3-build15" />