Skip to content

Commit

Permalink
nvdec: Adding Vp8 codec support (#2707)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
AcK77 authored Oct 12, 2021
1 parent a7109c7 commit d1604aa
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,26 @@
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()
Expand Down Expand Up @@ -115,7 +114,7 @@ public int DecodeFrame(Surface output, ReadOnlySpan<byte> bitstream)
{
_packet->data = ptr;
_packet->size = bitstream.Length;
result = _h264Decode(_context, output.Frame, &gotFrame, _packet);
result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
}

if (gotFrame == 0)
Expand All @@ -126,7 +125,7 @@ public int DecodeFrame(Surface output, ReadOnlySpan<byte> bitstream)
// 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.
Expand All @@ -138,6 +137,7 @@ public int DecodeFrame(Surface output, ReadOnlySpan<byte> bitstream)
if (gotFrame == 0)
{
ffmpeg.av_frame_unref(output.Frame);

return -1;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -11,7 +12,7 @@ public sealed class Decoder : IH264Decoder

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;
Expand All @@ -29,7 +30,7 @@ public bool Decode(ref H264PictureInfo pictureInfo, ISurface output, ReadOnlySpa
outSurf.RequestedHeight != _oldOutputHeight)
{
_context.Dispose();
_context = new FFmpegContext();
_context = new FFmpegContext(AVCodecID.AV_CODEC_ID_H264);

_oldOutputWidth = outSurf.RequestedWidth;
_oldOutputHeight = outSurf.RequestedHeight;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Numerics;

namespace Ryujinx.Graphics.Nvdec.H264
namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
{
struct H264BitStreamWriter
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Ryujinx.Graphics.Video;
using System;

namespace Ryujinx.Graphics.Nvdec.H264
namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
{
static class SpsAndPpsReconstruction
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</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>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Ryujinx.Graphics.Video;
using System;

namespace Ryujinx.Graphics.Nvdec.H264
namespace Ryujinx.Graphics.Nvdec.FFmpeg
{
unsafe class Surface : ISurface
{
Expand Down
53 changes: 53 additions & 0 deletions Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
6 changes: 3 additions & 3 deletions Ryujinx.Graphics.Nvdec/H264Decoder.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,7 +10,7 @@ static class H264Decoder
{
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();
Expand All @@ -25,7 +25,7 @@ public unsafe static void Decode(NvdecDecoderContext context, ResourceManager rm
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);

Expand Down
20 changes: 14 additions & 6 deletions Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
3 changes: 3 additions & 0 deletions Ryujinx.Graphics.Nvdec/NvdecDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ private void Decode(CodecId codecId)
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;
Expand Down
2 changes: 1 addition & 1 deletion Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
75 changes: 75 additions & 0 deletions Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs
Original file line number Diff line number Diff line change
@@ -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
};
}
}
}
33 changes: 33 additions & 0 deletions Ryujinx.Graphics.Nvdec/Vp8Decoder.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
11 changes: 11 additions & 0 deletions Ryujinx.Graphics.Video/Vp8PictureInfo.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit d1604aa

Please sign in to comment.