Add support for RGBA8 output format on the video image composer

This commit is contained in:
gdkchan 2018-10-21 18:59:43 -03:00
parent 83f43b4624
commit 4aa9ace3e3
10 changed files with 332 additions and 31 deletions

View file

@ -9,6 +9,10 @@ namespace Ryujinx.Graphics.VideoDecoding
private static AVCodec* Codec;
private static AVCodecContext* Context;
private static AVFrame* Frame;
private static SwsContext* ScalerCtx;
private static int ScalerWidth;
private static int ScalerHeight;
public static bool IsInitialized { get; private set; }
@ -29,9 +33,9 @@ namespace Ryujinx.Graphics.VideoDecoding
Uninitialize();
}
Codec = ffmpeg.avcodec_find_decoder(CodecId);
Context = ffmpeg.avcodec_alloc_context3(Codec);
Frame = ffmpeg.av_frame_alloc();
Codec = ffmpeg.avcodec_find_decoder(CodecId);
Context = ffmpeg.avcodec_alloc_context3(Codec);
Frame = ffmpeg.av_frame_alloc();
ffmpeg.avcodec_open2(Context, Codec, null);
@ -82,6 +86,61 @@ namespace Ryujinx.Graphics.VideoDecoding
};
}
public static FFmpegFrame GetFrameRgba()
{
if (!IsInitialized)
{
throw new InvalidOperationException("Tried to use uninitialized codec!");
}
AVFrame ManagedFrame = Marshal.PtrToStructure<AVFrame>((IntPtr)Frame);
EnsureScalerSetup(ManagedFrame.width, ManagedFrame.height);
byte*[] Data = ManagedFrame.data.ToArray();
int[] LineSizes = ManagedFrame.linesize.ToArray();
byte[] Dst = new byte[ManagedFrame.width * ManagedFrame.height * 4];
fixed (byte* Ptr = Dst)
{
byte*[] DstData = new byte*[] { Ptr };
int[] DstLineSizes = new int[] { ManagedFrame.width * 4 };
ffmpeg.sws_scale(ScalerCtx, Data, LineSizes, 0, ManagedFrame.height, DstData, DstLineSizes);
}
return new FFmpegFrame()
{
Width = ManagedFrame.width,
Height = ManagedFrame.height,
Data = Dst
};
}
private static void EnsureScalerSetup(int Width, int Height)
{
if (Width == 0 || Height == 0)
{
return;
}
if (ScalerCtx == null || ScalerWidth != Width || ScalerHeight != Height)
{
FreeScaler();
ScalerCtx = ffmpeg.sws_getContext(
Width, Height, AVPixelFormat.AV_PIX_FMT_YUV420P,
Width, Height, AVPixelFormat.AV_PIX_FMT_RGBA, 0, null, null, null);
ScalerWidth = Width;
ScalerHeight = Height;
}
}
public static void Uninitialize()
{
if (IsInitialized)
@ -90,8 +149,20 @@ namespace Ryujinx.Graphics.VideoDecoding
ffmpeg.av_free(Frame);
ffmpeg.avcodec_close(Context);
FreeScaler();
IsInitialized = false;
}
}
private static void FreeScaler()
{
if (ScalerCtx != null)
{
ffmpeg.sws_freeContext(ScalerCtx);
ScalerCtx = null;
}
}
}
}

View file

@ -8,5 +8,7 @@ namespace Ryujinx.Graphics.VideoDecoding
public byte* LumaPtr;
public byte* ChromaBPtr;
public byte* ChromaRPtr;
public byte[] Data;
}
}

View file

@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.VideoDecoding
{
class H264Decoder
{
private int MaxPicOrderCntLsbMinus4;
private int Log2MaxPicOrderCntLsbMinus4;
private bool DeltaPicOrderAlwaysZeroFlag;
private bool FrameMbsOnlyFlag;
private int PicWidthInMbs;
@ -35,9 +35,7 @@ namespace Ryujinx.Graphics.VideoDecoding
public void Decode(H264ParameterSets Params, H264Matrices Matrices, byte[] FrameData)
{
int LastFrameNumber = FrameNumber;
MaxPicOrderCntLsbMinus4 = Params.MaxPicOrderCntLsbMinus4;
Log2MaxPicOrderCntLsbMinus4 = Params.Log2MaxPicOrderCntLsbMinus4;
DeltaPicOrderAlwaysZeroFlag = Params.DeltaPicOrderAlwaysZeroFlag;
FrameMbsOnlyFlag = Params.FrameMbsOnlyFlag;
PicWidthInMbs = Params.PicWidthInMbs;
@ -117,7 +115,7 @@ namespace Ryujinx.Graphics.VideoDecoding
if (PicOrderCntType == 0)
{
Writer.WriteUe(MaxPicOrderCntLsbMinus4);
Writer.WriteUe(Log2MaxPicOrderCntLsbMinus4);
}
else if (PicOrderCntType == 1)
{
@ -130,7 +128,7 @@ namespace Ryujinx.Graphics.VideoDecoding
int PicHeightInMbs = PicHeightInMapUnits / (FrameMbsOnlyFlag ? 1 : 2);
Writer.WriteUe(4);
Writer.WriteUe(16);
Writer.WriteBit(false);
Writer.WriteUe(PicWidthInMbs - 1);
Writer.WriteUe(PicHeightInMbs - 1);

View file

@ -5,7 +5,7 @@ namespace Ryujinx.Graphics.VideoDecoding
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct H264ParameterSets
{
public int MaxPicOrderCntLsbMinus4;
public int Log2MaxPicOrderCntLsbMinus4;
public bool DeltaPicOrderAlwaysZeroFlag;
public bool FrameMbsOnlyFlag;
public int PicWidthInMbs;

View file

@ -1,4 +1,7 @@
using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Memory;
using Ryujinx.Graphics.Texture;
using Ryujinx.Graphics.VideoImageComposition;
using System;
using System.Runtime.InteropServices;
@ -195,30 +198,81 @@ namespace Ryujinx.Graphics.VideoDecoding
return (long)(uint)Arguments[0] << 8;
}
internal void CopyPlanes(NvGpuVmm Vmm, long LumaPlaneAddress, long ChromaPlaneAddress)
internal void CopyPlanes(NvGpuVmm Vmm, SurfaceOutputConfig OutputConfig)
{
switch (OutputConfig.PixelFormat)
{
case SurfacePixelFormat.RGBA8: CopyPlanesRgba8 (Vmm, OutputConfig); break;
case SurfacePixelFormat.YUV420P: CopyPlanesYuv420p(Vmm, OutputConfig); break;
default: ThrowUnimplementedPixelFormat(OutputConfig.PixelFormat); break;
}
}
private void CopyPlanesRgba8(NvGpuVmm Vmm, SurfaceOutputConfig OutputConfig)
{
FFmpegFrame Frame = FFmpegWrapper.GetFrameRgba();
if ((Frame.Width | Frame.Height) == 0)
{
return;
}
GalImage Image = new GalImage(
OutputConfig.SurfaceWidth,
OutputConfig.SurfaceHeight, 1,
OutputConfig.GobBlockHeight,
GalMemoryLayout.BlockLinear,
GalImageFormat.RGBA8 | GalImageFormat.Unorm);
ImageUtils.WriteTexture(Vmm, Image, Vmm.GetPhysicalAddress(OutputConfig.SurfaceLumaAddress), Frame.Data);
}
private void CopyPlanesYuv420p(NvGpuVmm Vmm, SurfaceOutputConfig OutputConfig)
{
FFmpegFrame Frame = FFmpegWrapper.GetFrame();
int HalfWidth = Frame.Width / 2;
int HalfHeight = Frame.Height / 2;
if ((Frame.Width | Frame.Height) == 0)
{
return;
}
int AlignedWidth = (Frame.Width + 0xff) & ~0xff;
if ((uint)OutputConfig.SurfaceWidth > (uint)Frame.Width ||
(uint)OutputConfig.SurfaceHeight > (uint)Frame.Height)
{
string Msg = "Surface and frame resolution mismatch!";
byte* LumaPtr = (byte*)Vmm.GetHostAddress(LumaPlaneAddress, AlignedWidth * Frame.Height);
byte* ChromaPtr = (byte*)Vmm.GetHostAddress(ChromaPlaneAddress, AlignedWidth * HalfHeight);
Msg += $" Surface resolution is {OutputConfig.SurfaceWidth}x{OutputConfig.SurfaceHeight},";
Msg += $" Frame resolution is {Frame.Width}x{Frame.Height}.";
throw new InvalidOperationException(Msg);
}
int HalfSrcWidth = Frame.Width / 2;
int HalfWidth = OutputConfig.SurfaceWidth / 2;
int HalfHeight = OutputConfig.SurfaceHeight / 2;
int AlignedWidth = (OutputConfig.SurfaceWidth + 0xff) & ~0xff;
byte* LumaPtr = (byte*)Vmm.GetHostAddress(OutputConfig.SurfaceLumaAddress, AlignedWidth * Frame.Height);
byte* ChromaPtr = (byte*)Vmm.GetHostAddress(OutputConfig.SurfaceChromaUAddress, AlignedWidth * HalfHeight);
for (int Y = 0; Y < Frame.Height; Y++)
{
int Src = Y * Frame.Width;
int Dst = Y * AlignedWidth;
Buffer.MemoryCopy(Frame.LumaPtr + Src, LumaPtr + Dst, Frame.Width, Frame.Width);
int Size = OutputConfig.SurfaceWidth;
Buffer.MemoryCopy(Frame.LumaPtr + Src, LumaPtr + Dst, Size, Size);
}
//Copy chroma data from both channels with interleaving.
for (int Y = 0; Y < HalfHeight; Y++)
{
int Src = Y * HalfWidth;
int Src = Y * HalfSrcWidth;
int Dst = Y * AlignedWidth;
for (int X = 0; X < HalfWidth; X++)
@ -233,5 +287,10 @@ namespace Ryujinx.Graphics.VideoDecoding
{
throw new NotImplementedException("Codec \"" + CurrentVideoCodec + "\" is not supported!");
}
private void ThrowUnimplementedPixelFormat(SurfacePixelFormat PixelFormat)
{
throw new NotImplementedException("Pixel format \"" + PixelFormat + "\" is not supported!");
}
}
}

View file

@ -0,0 +1,69 @@
using Ryujinx.Graphics.Memory;
using System;
namespace Ryujinx.Graphics.VideoImageComposition
{
class StructUnpacker
{
private NvGpuVmm Vmm;
private long Position;
private ulong Buffer;
private int BuffPos;
public StructUnpacker(NvGpuVmm Vmm, long Position)
{
this.Vmm = Vmm;
this.Position = Position;
BuffPos = 64;
}
public int Read(int Bits)
{
if ((uint)Bits > 32)
{
throw new ArgumentOutOfRangeException(nameof(Bits));
}
int Value = 0;
while (Bits > 0)
{
RefillBufferIfNeeded();
int ReadBits = Bits;
int MaxReadBits = 64 - BuffPos;
if (ReadBits > MaxReadBits)
{
ReadBits = MaxReadBits;
}
Value <<= ReadBits;
Value |= (int)(Buffer >> BuffPos) & (int)(0xffffffff >> (32 - ReadBits));
BuffPos += ReadBits;
Bits -= ReadBits;
}
return Value;
}
private void RefillBufferIfNeeded()
{
if (BuffPos >= 64)
{
Buffer = Vmm.ReadUInt64(Position);
Position += 8;
BuffPos = 0;
}
}
}
}

View file

@ -0,0 +1,33 @@
namespace Ryujinx.Graphics.VideoImageComposition
{
struct SurfaceOutputConfig
{
public SurfacePixelFormat PixelFormat;
public int SurfaceWidth;
public int SurfaceHeight;
public int GobBlockHeight;
public long SurfaceLumaAddress;
public long SurfaceChromaUAddress;
public long SurfaceChromaVAddress;
public SurfaceOutputConfig(
SurfacePixelFormat PixelFormat,
int SurfaceWidth,
int SurfaceHeight,
int GobBlockHeight,
long OutputSurfaceLumaAddress,
long OutputSurfaceChromaUAddress,
long OutputSurfaceChromaVAddress)
{
this.PixelFormat = PixelFormat;
this.SurfaceWidth = SurfaceWidth;
this.SurfaceHeight = SurfaceHeight;
this.GobBlockHeight = GobBlockHeight;
this.SurfaceLumaAddress = OutputSurfaceLumaAddress;
this.SurfaceChromaUAddress = OutputSurfaceChromaUAddress;
this.SurfaceChromaVAddress = OutputSurfaceChromaVAddress;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.VideoImageComposition
{
enum SurfacePixelFormat
{
RGBA8 = 0x1f,
YUV420P = 0x44
}
}

View file

@ -6,8 +6,10 @@ namespace Ryujinx.Graphics.VideoImageComposition
{
private NvGpu Gpu;
private long LumaPlaneAddress;
private long ChromaPlaneAddress;
private long ConfigStructAddress;
private long OutputSurfaceLumaAddress;
private long OutputSurfaceChromaUAddress;
private long OutputSurfaceChromaVAddress;
public VideoImageComposer(NvGpu Gpu)
{
@ -20,25 +22,81 @@ namespace Ryujinx.Graphics.VideoImageComposition
switch (Method)
{
case VideoImageComposerMeth.Execute: Execute (Vmm, Arguments); break;
case VideoImageComposerMeth.SetVDecLumaPlaneAddr: SetVDecLumaPlaneAddr (Vmm, Arguments); break;
case VideoImageComposerMeth.SetVDecChromaPlaneAddr: SetVDecChromaPlaneAddr(Vmm, Arguments); break;
case VideoImageComposerMeth.Execute:
Execute(Vmm, Arguments);
break;
case VideoImageComposerMeth.SetConfigStructOffset:
SetConfigStructOffset(Vmm, Arguments);
break;
case VideoImageComposerMeth.SetOutputSurfaceLumaOffset:
SetOutputSurfaceLumaOffset(Vmm, Arguments);
break;
case VideoImageComposerMeth.SetOutputSurfaceChromaUOffset:
SetOutputSurfaceChromaUOffset(Vmm, Arguments);
break;
case VideoImageComposerMeth.SetOutputSurfaceChromaVOffset:
SetOutputSurfaceChromaVOffset(Vmm, Arguments);
break;
}
}
private void Execute(NvGpuVmm Vmm, int[] Arguments)
{
Gpu.VideoDecoder.CopyPlanes(Vmm, LumaPlaneAddress, ChromaPlaneAddress);
StructUnpacker Unpacker = new StructUnpacker(Vmm, ConfigStructAddress + 0x20);
SurfacePixelFormat PixelFormat = (SurfacePixelFormat)Unpacker.Read(7);
int ChromaLocHoriz = Unpacker.Read(2);
int ChromaLocVert = Unpacker.Read(2);
int BlockLinearKind = Unpacker.Read(4);
int BlockLinearHeightLog2 = Unpacker.Read(4);
int Reserved0 = Unpacker.Read(3);
int Reserved1 = Unpacker.Read(10);
int SurfaceWidthMinus1 = Unpacker.Read(14);
int SurfaceHeightMinus1 = Unpacker.Read(14);
int GobBlockHeight = 1 << BlockLinearHeightLog2;
int SurfaceWidth = SurfaceWidthMinus1 + 1;
int SurfaceHeight = SurfaceHeightMinus1 + 1;
SurfaceOutputConfig OutputConfig = new SurfaceOutputConfig(
PixelFormat,
SurfaceWidth,
SurfaceHeight,
GobBlockHeight,
OutputSurfaceLumaAddress,
OutputSurfaceChromaUAddress,
OutputSurfaceChromaVAddress);
Gpu.VideoDecoder.CopyPlanes(Vmm, OutputConfig);
}
private void SetVDecLumaPlaneAddr(NvGpuVmm Vmm, int[] Arguments)
private void SetConfigStructOffset(NvGpuVmm Vmm, int[] Arguments)
{
LumaPlaneAddress = GetAddress(Arguments);
ConfigStructAddress = GetAddress(Arguments);
}
private void SetVDecChromaPlaneAddr(NvGpuVmm Vmm, int[] Arguments)
private void SetOutputSurfaceLumaOffset(NvGpuVmm Vmm, int[] Arguments)
{
ChromaPlaneAddress = GetAddress(Arguments);
OutputSurfaceLumaAddress = GetAddress(Arguments);
}
private void SetOutputSurfaceChromaUOffset(NvGpuVmm Vmm, int[] Arguments)
{
OutputSurfaceChromaUAddress = GetAddress(Arguments);
}
private void SetOutputSurfaceChromaVOffset(NvGpuVmm Vmm, int[] Arguments)
{
OutputSurfaceChromaVAddress = GetAddress(Arguments);
}
private static long GetAddress(int[] Arguments)

View file

@ -2,8 +2,11 @@ namespace Ryujinx.Graphics.VideoImageComposition
{
enum VideoImageComposerMeth
{
Execute = 0xc0,
SetVDecLumaPlaneAddr = 0x1c8,
SetVDecChromaPlaneAddr = 0x1c9
Execute = 0xc0,
SetControlParams = 0x1c1,
SetConfigStructOffset = 0x1c2,
SetOutputSurfaceLumaOffset = 0x1c8,
SetOutputSurfaceChromaUOffset = 0x1c9,
SetOutputSurfaceChromaVOffset = 0x1ca
}
}