NVDEC implementation using FFmpeg (#443)

* Initial nvdec implementation using FFmpeg

* Fix swapped channels on the video decoder and the G8R8 texture format

* Fix texture samplers not being set properly (regression)

* Rebased

* Remove unused code introduced on the rebase

* Add support for RGBA8 output format on the video image composer

* Correct spacing

* Some fixes for rebase and other tweaks

* Allow size mismatch on frame copy

* Get rid of GetHostAddress calls on VDec
This commit is contained in:
gdkchan 2018-12-03 00:38:47 -02:00 committed by GitHub
parent ad00fd0244
commit c86aacde76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 2795 additions and 76 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,138 @@
using System;
using System.Diagnostics;
namespace Ryujinx.Graphics.Texture
{
class ASTCPixel
{
public short R { get; set; }
public short G { get; set; }
public short B { get; set; }
public short A { get; set; }
byte[] BitDepth = new byte[4];
public ASTCPixel(short _A, short _R, short _G, short _B)
{
A = _A;
R = _R;
G = _G;
B = _B;
for (int i = 0; i < 4; i++)
BitDepth[i] = 8;
}
public void ClampByte()
{
R = Math.Min(Math.Max(R, (short)0), (short)255);
G = Math.Min(Math.Max(G, (short)0), (short)255);
B = Math.Min(Math.Max(B, (short)0), (short)255);
A = Math.Min(Math.Max(A, (short)0), (short)255);
}
public short GetComponent(int Index)
{
switch(Index)
{
case 0: return A;
case 1: return R;
case 2: return G;
case 3: return B;
}
return 0;
}
public void SetComponent(int Index, int Value)
{
switch (Index)
{
case 0:
A = (short)Value;
break;
case 1:
R = (short)Value;
break;
case 2:
G = (short)Value;
break;
case 3:
B = (short)Value;
break;
}
}
public void ChangeBitDepth(byte[] Depth)
{
for(int i = 0; i< 4; i++)
{
int Value = ChangeBitDepth(GetComponent(i), BitDepth[i], Depth[i]);
SetComponent(i, Value);
BitDepth[i] = Depth[i];
}
}
short ChangeBitDepth(short Value, byte OldDepth, byte NewDepth)
{
Debug.Assert(NewDepth <= 8);
Debug.Assert(OldDepth <= 8);
if (OldDepth == NewDepth)
{
// Do nothing
return Value;
}
else if (OldDepth == 0 && NewDepth != 0)
{
return (short)((1 << NewDepth) - 1);
}
else if (NewDepth > OldDepth)
{
return (short)BitArrayStream.Replicate(Value, OldDepth, NewDepth);
}
else
{
// oldDepth > newDepth
if (NewDepth == 0)
{
return 0xFF;
}
else
{
byte BitsWasted = (byte)(OldDepth - NewDepth);
short TempValue = Value;
TempValue = (short)((TempValue + (1 << (BitsWasted - 1))) >> BitsWasted);
TempValue = Math.Min(Math.Max((short)0, TempValue), (short)((1 << NewDepth) - 1));
return (byte)(TempValue);
}
}
}
public int Pack()
{
ASTCPixel NewPixel = new ASTCPixel(A, R, G, B);
byte[] eightBitDepth = { 8, 8, 8, 8 };
NewPixel.ChangeBitDepth(eightBitDepth);
return (byte)NewPixel.A << 24 |
(byte)NewPixel.B << 16 |
(byte)NewPixel.G << 8 |
(byte)NewPixel.R << 0;
}
// Adds more precision to the blue channel as described
// in C.2.14
public static ASTCPixel BlueContract(int a, int r, int g, int b)
{
return new ASTCPixel((short)(a),
(short)((r + b) >> 1),
(short)((g + b) >> 1),
(short)(b));
}
}
}

View file

@ -0,0 +1,121 @@
using System;
using System.Collections;
namespace Ryujinx.Graphics.Texture
{
public class BitArrayStream
{
public BitArray BitsArray;
public int Position { get; private set; }
public BitArrayStream(BitArray BitArray)
{
BitsArray = BitArray;
Position = 0;
}
public short ReadBits(int Length)
{
int RetValue = 0;
for (int i = Position; i < Position + Length; i++)
{
if (BitsArray[i])
{
RetValue |= 1 << (i - Position);
}
}
Position += Length;
return (short)RetValue;
}
public int ReadBits(int Start, int End)
{
int RetValue = 0;
for (int i = Start; i <= End; i++)
{
if (BitsArray[i])
{
RetValue |= 1 << (i - Start);
}
}
return RetValue;
}
public int ReadBit(int Index)
{
return Convert.ToInt32(BitsArray[Index]);
}
public void WriteBits(int Value, int Length)
{
for (int i = Position; i < Position + Length; i++)
{
BitsArray[i] = ((Value >> (i - Position)) & 1) != 0;
}
Position += Length;
}
public byte[] ToByteArray()
{
byte[] RetArray = new byte[(BitsArray.Length + 7) / 8];
BitsArray.CopyTo(RetArray, 0);
return RetArray;
}
public static int Replicate(int Value, int NumberBits, int ToBit)
{
if (NumberBits == 0) return 0;
if (ToBit == 0) return 0;
int TempValue = Value & ((1 << NumberBits) - 1);
int RetValue = TempValue;
int ResLength = NumberBits;
while (ResLength < ToBit)
{
int Comp = 0;
if (NumberBits > ToBit - ResLength)
{
int NewShift = ToBit - ResLength;
Comp = NumberBits - NewShift;
NumberBits = NewShift;
}
RetValue <<= NumberBits;
RetValue |= TempValue >> Comp;
ResLength += NumberBits;
}
return RetValue;
}
public static int PopCnt(int Number)
{
int Counter;
for (Counter = 0; Number != 0; Counter++)
{
Number &= Number - 1;
}
return Counter;
}
public static void Swap<T>(ref T lhs, ref T rhs)
{
T Temp = lhs;
lhs = rhs;
rhs = Temp;
}
// Transfers a bit as described in C.2.14
public static void BitTransferSigned(ref int a, ref int b)
{
b >>= 1;
b |= a & 0x80;
a >>= 1;
a &= 0x3F;
if ((a & 0x20) != 0) a -= 0x40;
}
}
}

View file

@ -0,0 +1,59 @@
using System;
namespace Ryujinx.Graphics.Texture
{
class BlockLinearSwizzle : ISwizzle
{
private int BhShift;
private int BppShift;
private int BhMask;
private int XShift;
private int GobStride;
public BlockLinearSwizzle(int Width, int Bpp, int BlockHeight = 16)
{
BhMask = (BlockHeight * 8) - 1;
BhShift = CountLsbZeros(BlockHeight * 8);
BppShift = CountLsbZeros(Bpp);
int WidthInGobs = (int)MathF.Ceiling(Width * Bpp / 64f);
GobStride = 512 * BlockHeight * WidthInGobs;
XShift = CountLsbZeros(512 * BlockHeight);
}
private int CountLsbZeros(int Value)
{
int Count = 0;
while (((Value >> Count) & 1) == 0)
{
Count++;
}
return Count;
}
public int GetSwizzleOffset(int X, int Y)
{
X <<= BppShift;
int Position = (Y >> BhShift) * GobStride;
Position += (X >> 6) << XShift;
Position += ((Y & BhMask) >> 3) << 9;
Position += ((X & 0x3f) >> 5) << 8;
Position += ((Y & 0x07) >> 1) << 6;
Position += ((X & 0x1f) >> 4) << 5;
Position += ((Y & 0x01) >> 0) << 4;
Position += ((X & 0x0f) >> 0) << 0;
return Position;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.Graphics.Texture
{
interface ISwizzle
{
int GetSwizzleOffset(int X, int Y);
}
}

View file

@ -0,0 +1,445 @@
using ChocolArm64.Memory;
using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Memory;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Texture
{
public static class ImageUtils
{
[Flags]
private enum TargetBuffer
{
Color = 1 << 0,
Depth = 1 << 1,
Stencil = 1 << 2,
DepthStencil = Depth | Stencil
}
private struct ImageDescriptor
{
public int BytesPerPixel { get; private set; }
public int BlockWidth { get; private set; }
public int BlockHeight { get; private set; }
public TargetBuffer Target { get; private set; }
public ImageDescriptor(int BytesPerPixel, int BlockWidth, int BlockHeight, TargetBuffer Target)
{
this.BytesPerPixel = BytesPerPixel;
this.BlockWidth = BlockWidth;
this.BlockHeight = BlockHeight;
this.Target = Target;
}
}
private const GalImageFormat Snorm = GalImageFormat.Snorm;
private const GalImageFormat Unorm = GalImageFormat.Unorm;
private const GalImageFormat Sint = GalImageFormat.Sint;
private const GalImageFormat Uint = GalImageFormat.Uint;
private const GalImageFormat Float = GalImageFormat.Float;
private const GalImageFormat Srgb = GalImageFormat.Srgb;
private static readonly Dictionary<GalTextureFormat, GalImageFormat> s_TextureTable =
new Dictionary<GalTextureFormat, GalImageFormat>()
{
{ GalTextureFormat.RGBA32, GalImageFormat.RGBA32 | Sint | Uint | Float },
{ GalTextureFormat.RGBA16, GalImageFormat.RGBA16 | Snorm | Unorm | Sint | Uint | Float },
{ GalTextureFormat.RG32, GalImageFormat.RG32 | Sint | Uint | Float },
{ GalTextureFormat.RGBA8, GalImageFormat.RGBA8 | Snorm | Unorm | Sint | Uint | Srgb },
{ GalTextureFormat.RGB10A2, GalImageFormat.RGB10A2 | Snorm | Unorm | Sint | Uint },
{ GalTextureFormat.RG8, GalImageFormat.RG8 | Snorm | Unorm | Sint | Uint },
{ GalTextureFormat.R16, GalImageFormat.R16 | Snorm | Unorm | Sint | Uint | Float },
{ GalTextureFormat.R8, GalImageFormat.R8 | Snorm | Unorm | Sint | Uint },
{ GalTextureFormat.RG16, GalImageFormat.RG16 | Snorm | Unorm | Float },
{ GalTextureFormat.R32, GalImageFormat.R32 | Sint | Uint | Float },
{ GalTextureFormat.RGBA4, GalImageFormat.RGBA4 | Unorm },
{ GalTextureFormat.RGB5A1, GalImageFormat.RGB5A1 | Unorm },
{ GalTextureFormat.RGB565, GalImageFormat.RGB565 | Unorm },
{ GalTextureFormat.R11G11B10F, GalImageFormat.R11G11B10 | Float },
{ GalTextureFormat.D24S8, GalImageFormat.D24S8 | Unorm | Uint },
{ GalTextureFormat.D32F, GalImageFormat.D32 | Float },
{ GalTextureFormat.D32FX24S8, GalImageFormat.D32S8 | Float },
{ GalTextureFormat.D16, GalImageFormat.D16 | Unorm },
//Compressed formats
{ GalTextureFormat.BptcSfloat, GalImageFormat.BptcSfloat | Float },
{ GalTextureFormat.BptcUfloat, GalImageFormat.BptcUfloat | Float },
{ GalTextureFormat.BptcUnorm, GalImageFormat.BptcUnorm | Unorm | Srgb },
{ GalTextureFormat.BC1, GalImageFormat.BC1 | Unorm | Srgb },
{ GalTextureFormat.BC2, GalImageFormat.BC2 | Unorm | Srgb },
{ GalTextureFormat.BC3, GalImageFormat.BC3 | Unorm | Srgb },
{ GalTextureFormat.BC4, GalImageFormat.BC4 | Unorm | Snorm },
{ GalTextureFormat.BC5, GalImageFormat.BC5 | Unorm | Snorm },
{ GalTextureFormat.Astc2D4x4, GalImageFormat.Astc2D4x4 | Unorm | Srgb },
{ GalTextureFormat.Astc2D5x5, GalImageFormat.Astc2D5x5 | Unorm | Srgb },
{ GalTextureFormat.Astc2D6x6, GalImageFormat.Astc2D6x6 | Unorm | Srgb },
{ GalTextureFormat.Astc2D8x8, GalImageFormat.Astc2D8x8 | Unorm | Srgb },
{ GalTextureFormat.Astc2D10x10, GalImageFormat.Astc2D10x10 | Unorm | Srgb },
{ GalTextureFormat.Astc2D12x12, GalImageFormat.Astc2D12x12 | Unorm | Srgb },
{ GalTextureFormat.Astc2D5x4, GalImageFormat.Astc2D5x4 | Unorm | Srgb },
{ GalTextureFormat.Astc2D6x5, GalImageFormat.Astc2D6x5 | Unorm | Srgb },
{ GalTextureFormat.Astc2D8x6, GalImageFormat.Astc2D8x6 | Unorm | Srgb },
{ GalTextureFormat.Astc2D10x8, GalImageFormat.Astc2D10x8 | Unorm | Srgb },
{ GalTextureFormat.Astc2D12x10, GalImageFormat.Astc2D12x10 | Unorm | Srgb },
{ GalTextureFormat.Astc2D8x5, GalImageFormat.Astc2D8x5 | Unorm | Srgb },
{ GalTextureFormat.Astc2D10x5, GalImageFormat.Astc2D10x5 | Unorm | Srgb },
{ GalTextureFormat.Astc2D10x6, GalImageFormat.Astc2D10x6 | Unorm | Srgb }
};
private static readonly Dictionary<GalImageFormat, ImageDescriptor> s_ImageTable =
new Dictionary<GalImageFormat, ImageDescriptor>()
{
{ GalImageFormat.RGBA32, new ImageDescriptor(16, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RGBA16, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RG32, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RGBX8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RGBA8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.BGRA8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RGB10A2, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.R32, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RGBA4, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.BptcSfloat, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.BptcUfloat, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.BGR5A1, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RGB5A1, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RGB565, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.BptcUnorm, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.RG16, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.RG8, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.R16, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.R8, new ImageDescriptor(1, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.R11G11B10, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
{ GalImageFormat.BC1, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.BC2, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.BC3, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.BC4, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.BC5, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.Astc2D4x4, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) },
{ GalImageFormat.Astc2D5x5, new ImageDescriptor(16, 5, 5, TargetBuffer.Color) },
{ GalImageFormat.Astc2D6x6, new ImageDescriptor(16, 6, 6, TargetBuffer.Color) },
{ GalImageFormat.Astc2D8x8, new ImageDescriptor(16, 8, 8, TargetBuffer.Color) },
{ GalImageFormat.Astc2D10x10, new ImageDescriptor(16, 10, 10, TargetBuffer.Color) },
{ GalImageFormat.Astc2D12x12, new ImageDescriptor(16, 12, 12, TargetBuffer.Color) },
{ GalImageFormat.Astc2D5x4, new ImageDescriptor(16, 5, 4, TargetBuffer.Color) },
{ GalImageFormat.Astc2D6x5, new ImageDescriptor(16, 6, 5, TargetBuffer.Color) },
{ GalImageFormat.Astc2D8x6, new ImageDescriptor(16, 8, 6, TargetBuffer.Color) },
{ GalImageFormat.Astc2D10x8, new ImageDescriptor(16, 10, 8, TargetBuffer.Color) },
{ GalImageFormat.Astc2D12x10, new ImageDescriptor(16, 12, 10, TargetBuffer.Color) },
{ GalImageFormat.Astc2D8x5, new ImageDescriptor(16, 8, 5, TargetBuffer.Color) },
{ GalImageFormat.Astc2D10x5, new ImageDescriptor(16, 10, 5, TargetBuffer.Color) },
{ GalImageFormat.Astc2D10x6, new ImageDescriptor(16, 10, 6, TargetBuffer.Color) },
{ GalImageFormat.D16, new ImageDescriptor(2, 1, 1, TargetBuffer.Depth) },
{ GalImageFormat.D24, new ImageDescriptor(4, 1, 1, TargetBuffer.Depth) },
{ GalImageFormat.D24S8, new ImageDescriptor(4, 1, 1, TargetBuffer.DepthStencil) },
{ GalImageFormat.D32, new ImageDescriptor(4, 1, 1, TargetBuffer.Depth) },
{ GalImageFormat.D32S8, new ImageDescriptor(8, 1, 1, TargetBuffer.DepthStencil) }
};
public static GalImageFormat ConvertTexture(
GalTextureFormat Format,
GalTextureType RType,
GalTextureType GType,
GalTextureType BType,
GalTextureType AType,
bool ConvSrgb)
{
if (!s_TextureTable.TryGetValue(Format, out GalImageFormat ImageFormat))
{
throw new NotImplementedException($"Format 0x{((int)Format):x} not implemented!");
}
if (!HasDepth(ImageFormat) && (RType != GType || RType != BType || RType != AType))
{
throw new NotImplementedException($"Per component types are not implemented!");
}
GalImageFormat FormatType = ConvSrgb ? Srgb : GetFormatType(RType);
GalImageFormat CombinedFormat = (ImageFormat & GalImageFormat.FormatMask) | FormatType;
if (!ImageFormat.HasFlag(FormatType))
{
throw new NotImplementedException($"Format \"{CombinedFormat}\" not implemented!");
}
return CombinedFormat;
}
public static GalImageFormat ConvertSurface(GalSurfaceFormat Format)
{
switch (Format)
{
case GalSurfaceFormat.RGBA32Float: return GalImageFormat.RGBA32 | Float;
case GalSurfaceFormat.RGBA32Uint: return GalImageFormat.RGBA32 | Uint;
case GalSurfaceFormat.RGBA16Float: return GalImageFormat.RGBA16 | Float;
case GalSurfaceFormat.RGBA16Unorm: return GalImageFormat.RGBA16 | Unorm;
case GalSurfaceFormat.RG32Float: return GalImageFormat.RG32 | Float;
case GalSurfaceFormat.RG32Sint: return GalImageFormat.RG32 | Sint;
case GalSurfaceFormat.RG32Uint: return GalImageFormat.RG32 | Uint;
case GalSurfaceFormat.BGRA8Unorm: return GalImageFormat.BGRA8 | Unorm;
case GalSurfaceFormat.BGRA8Srgb: return GalImageFormat.BGRA8 | Srgb;
case GalSurfaceFormat.RGB10A2Unorm: return GalImageFormat.RGB10A2 | Unorm;
case GalSurfaceFormat.RGBA8Unorm: return GalImageFormat.RGBA8 | Unorm;
case GalSurfaceFormat.RGBA8Srgb: return GalImageFormat.RGBA8 | Srgb;
case GalSurfaceFormat.RGBA8Snorm: return GalImageFormat.RGBA8 | Snorm;
case GalSurfaceFormat.RG16Snorm: return GalImageFormat.RG16 | Snorm;
case GalSurfaceFormat.RG16Unorm: return GalImageFormat.RG16 | Unorm;
case GalSurfaceFormat.RG16Float: return GalImageFormat.RG16 | Float;
case GalSurfaceFormat.R11G11B10Float: return GalImageFormat.R11G11B10 | Float;
case GalSurfaceFormat.R32Float: return GalImageFormat.R32 | Float;
case GalSurfaceFormat.R32Uint: return GalImageFormat.R32 | Uint;
case GalSurfaceFormat.RG8Unorm: return GalImageFormat.RG8 | Unorm;
case GalSurfaceFormat.RG8Snorm: return GalImageFormat.RG8 | Snorm;
case GalSurfaceFormat.R16Float: return GalImageFormat.R16 | Float;
case GalSurfaceFormat.R16Unorm: return GalImageFormat.R16 | Unorm;
case GalSurfaceFormat.R16Uint: return GalImageFormat.R16 | Uint;
case GalSurfaceFormat.R8Unorm: return GalImageFormat.R8 | Unorm;
case GalSurfaceFormat.R8Uint: return GalImageFormat.R8 | Uint;
case GalSurfaceFormat.B5G6R5Unorm: return GalImageFormat.RGB565 | Unorm;
case GalSurfaceFormat.BGR5A1Unorm: return GalImageFormat.BGR5A1 | Unorm;
case GalSurfaceFormat.RGBX8Unorm: return GalImageFormat.RGBX8 | Unorm;
}
throw new NotImplementedException(Format.ToString());
}
public static GalImageFormat ConvertZeta(GalZetaFormat Format)
{
switch (Format)
{
case GalZetaFormat.D32Float: return GalImageFormat.D32 | Float;
case GalZetaFormat.S8D24Unorm: return GalImageFormat.D24S8 | Unorm;
case GalZetaFormat.D16Unorm: return GalImageFormat.D16 | Unorm;
case GalZetaFormat.D24X8Unorm: return GalImageFormat.D24 | Unorm;
case GalZetaFormat.D24S8Unorm: return GalImageFormat.D24S8 | Unorm;
case GalZetaFormat.D32S8X24Float: return GalImageFormat.D32S8 | Float;
}
throw new NotImplementedException(Format.ToString());
}
public static byte[] ReadTexture(IMemory Memory, GalImage Image, long Position)
{
MemoryManager CpuMemory;
if (Memory is NvGpuVmm Vmm)
{
CpuMemory = Vmm.Memory;
}
else
{
CpuMemory = (MemoryManager)Memory;
}
ISwizzle Swizzle = TextureHelper.GetSwizzle(Image);
ImageDescriptor Desc = GetImageDescriptor(Image.Format);
(int Width, int Height) = GetImageSizeInBlocks(Image);
int BytesPerPixel = Desc.BytesPerPixel;
//Note: Each row of the texture needs to be aligned to 4 bytes.
int Pitch = (Width * BytesPerPixel + 3) & ~3;
byte[] Data = new byte[Height * Pitch];
for (int Y = 0; Y < Height; Y++)
{
int OutOffs = Y * Pitch;
for (int X = 0; X < Width; X++)
{
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
CpuMemory.ReadBytes(Position + Offset, Data, OutOffs, BytesPerPixel);
OutOffs += BytesPerPixel;
}
}
return Data;
}
public static void WriteTexture(NvGpuVmm Vmm, GalImage Image, long Position, byte[] Data)
{
ISwizzle Swizzle = TextureHelper.GetSwizzle(Image);
ImageDescriptor Desc = GetImageDescriptor(Image.Format);
(int Width, int Height) = ImageUtils.GetImageSizeInBlocks(Image);
int BytesPerPixel = Desc.BytesPerPixel;
int InOffs = 0;
for (int Y = 0; Y < Height; Y++)
for (int X = 0; X < Width; X++)
{
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
Vmm.Memory.WriteBytes(Position + Offset, Data, InOffs, BytesPerPixel);
InOffs += BytesPerPixel;
}
}
public static bool CopyTexture(
NvGpuVmm Vmm,
GalImage SrcImage,
GalImage DstImage,
long SrcAddress,
long DstAddress,
int SrcX,
int SrcY,
int DstX,
int DstY,
int Width,
int Height)
{
ISwizzle SrcSwizzle = TextureHelper.GetSwizzle(SrcImage);
ISwizzle DstSwizzle = TextureHelper.GetSwizzle(DstImage);
ImageDescriptor Desc = GetImageDescriptor(SrcImage.Format);
if (GetImageDescriptor(DstImage.Format).BytesPerPixel != Desc.BytesPerPixel)
{
return false;
}
int BytesPerPixel = Desc.BytesPerPixel;
for (int Y = 0; Y < Height; Y++)
for (int X = 0; X < Width; X++)
{
long SrcOffset = (uint)SrcSwizzle.GetSwizzleOffset(SrcX + X, SrcY + Y);
long DstOffset = (uint)DstSwizzle.GetSwizzleOffset(DstX + X, DstY + Y);
byte[] Texel = Vmm.ReadBytes(SrcAddress + SrcOffset, BytesPerPixel);
Vmm.WriteBytes(DstAddress + DstOffset, Texel);
}
return true;
}
public static int GetSize(GalImage Image)
{
ImageDescriptor Desc = GetImageDescriptor(Image.Format);
int Width = DivRoundUp(Image.Width, Desc.BlockWidth);
int Height = DivRoundUp(Image.Height, Desc.BlockHeight);
return Desc.BytesPerPixel * Width * Height;
}
public static int GetPitch(GalImageFormat Format, int Width)
{
ImageDescriptor Desc = GetImageDescriptor(Format);
int Pitch = Desc.BytesPerPixel * DivRoundUp(Width, Desc.BlockWidth);
Pitch = (Pitch + 0x1f) & ~0x1f;
return Pitch;
}
public static int GetBlockWidth(GalImageFormat Format)
{
return GetImageDescriptor(Format).BlockWidth;
}
public static int GetBlockHeight(GalImageFormat Format)
{
return GetImageDescriptor(Format).BlockHeight;
}
public static int GetAlignedWidth(GalImage Image)
{
ImageDescriptor Desc = GetImageDescriptor(Image.Format);
int AlignMask;
if (Image.Layout == GalMemoryLayout.BlockLinear)
{
AlignMask = Image.TileWidth * (64 / Desc.BytesPerPixel) - 1;
}
else
{
AlignMask = (32 / Desc.BytesPerPixel) - 1;
}
return (Image.Width + AlignMask) & ~AlignMask;
}
public static (int Width, int Height) GetImageSizeInBlocks(GalImage Image)
{
ImageDescriptor Desc = GetImageDescriptor(Image.Format);
return (DivRoundUp(Image.Width, Desc.BlockWidth),
DivRoundUp(Image.Height, Desc.BlockHeight));
}
public static int GetBytesPerPixel(GalImageFormat Format)
{
return GetImageDescriptor(Format).BytesPerPixel;
}
private static int DivRoundUp(int LHS, int RHS)
{
return (LHS + (RHS - 1)) / RHS;
}
public static bool HasColor(GalImageFormat Format)
{
return (GetImageDescriptor(Format).Target & TargetBuffer.Color) != 0;
}
public static bool HasDepth(GalImageFormat Format)
{
return (GetImageDescriptor(Format).Target & TargetBuffer.Depth) != 0;
}
public static bool HasStencil(GalImageFormat Format)
{
return (GetImageDescriptor(Format).Target & TargetBuffer.Stencil) != 0;
}
public static bool IsCompressed(GalImageFormat Format)
{
ImageDescriptor Desc = GetImageDescriptor(Format);
return (Desc.BlockWidth | Desc.BlockHeight) != 1;
}
private static ImageDescriptor GetImageDescriptor(GalImageFormat Format)
{
GalImageFormat PixelFormat = Format & GalImageFormat.FormatMask;
if (s_ImageTable.TryGetValue(PixelFormat, out ImageDescriptor Descriptor))
{
return Descriptor;
}
throw new NotImplementedException($"Format \"{PixelFormat}\" not implemented!");
}
private static GalImageFormat GetFormatType(GalTextureType Type)
{
switch (Type)
{
case GalTextureType.Snorm: return Snorm;
case GalTextureType.Unorm: return Unorm;
case GalTextureType.Sint: return Sint;
case GalTextureType.Uint: return Uint;
case GalTextureType.Float: return Float;
default: throw new NotImplementedException(((int)Type).ToString());
}
}
}
}

View file

@ -0,0 +1,269 @@
using System.Collections;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Texture
{
public struct IntegerEncoded
{
public enum EIntegerEncoding
{
JustBits,
Quint,
Trit
}
EIntegerEncoding Encoding;
public int NumberBits { get; private set; }
public int BitValue { get; private set; }
public int TritValue { get; private set; }
public int QuintValue { get; private set; }
public IntegerEncoded(EIntegerEncoding _Encoding, int NumBits)
{
Encoding = _Encoding;
NumberBits = NumBits;
BitValue = 0;
TritValue = 0;
QuintValue = 0;
}
public bool MatchesEncoding(IntegerEncoded Other)
{
return Encoding == Other.Encoding && NumberBits == Other.NumberBits;
}
public EIntegerEncoding GetEncoding()
{
return Encoding;
}
public int GetBitLength(int NumberVals)
{
int TotalBits = NumberBits * NumberVals;
if (Encoding == EIntegerEncoding.Trit)
{
TotalBits += (NumberVals * 8 + 4) / 5;
}
else if (Encoding == EIntegerEncoding.Quint)
{
TotalBits += (NumberVals * 7 + 2) / 3;
}
return TotalBits;
}
public static IntegerEncoded CreateEncoding(int MaxVal)
{
while (MaxVal > 0)
{
int Check = MaxVal + 1;
// Is maxVal a power of two?
if ((Check & (Check - 1)) == 0)
{
return new IntegerEncoded(EIntegerEncoding.JustBits, BitArrayStream.PopCnt(MaxVal));
}
// Is maxVal of the type 3*2^n - 1?
if ((Check % 3 == 0) && ((Check / 3) & ((Check / 3) - 1)) == 0)
{
return new IntegerEncoded(EIntegerEncoding.Trit, BitArrayStream.PopCnt(Check / 3 - 1));
}
// Is maxVal of the type 5*2^n - 1?
if ((Check % 5 == 0) && ((Check / 5) & ((Check / 5) - 1)) == 0)
{
return new IntegerEncoded(EIntegerEncoding.Quint, BitArrayStream.PopCnt(Check / 5 - 1));
}
// Apparently it can't be represented with a bounded integer sequence...
// just iterate.
MaxVal--;
}
return new IntegerEncoded(EIntegerEncoding.JustBits, 0);
}
public static void DecodeTritBlock(
BitArrayStream BitStream,
List<IntegerEncoded> ListIntegerEncoded,
int NumberBitsPerValue)
{
// Implement the algorithm in section C.2.12
int[] m = new int[5];
int[] t = new int[5];
int T;
// Read the trit encoded block according to
// table C.2.14
m[0] = BitStream.ReadBits(NumberBitsPerValue);
T = BitStream.ReadBits(2);
m[1] = BitStream.ReadBits(NumberBitsPerValue);
T |= BitStream.ReadBits(2) << 2;
m[2] = BitStream.ReadBits(NumberBitsPerValue);
T |= BitStream.ReadBits(1) << 4;
m[3] = BitStream.ReadBits(NumberBitsPerValue);
T |= BitStream.ReadBits(2) << 5;
m[4] = BitStream.ReadBits(NumberBitsPerValue);
T |= BitStream.ReadBits(1) << 7;
int C = 0;
BitArrayStream Tb = new BitArrayStream(new BitArray(new int[] { T }));
if (Tb.ReadBits(2, 4) == 7)
{
C = (Tb.ReadBits(5, 7) << 2) | Tb.ReadBits(0, 1);
t[4] = t[3] = 2;
}
else
{
C = Tb.ReadBits(0, 4);
if (Tb.ReadBits(5, 6) == 3)
{
t[4] = 2;
t[3] = Tb.ReadBit(7);
}
else
{
t[4] = Tb.ReadBit(7);
t[3] = Tb.ReadBits(5, 6);
}
}
BitArrayStream Cb = new BitArrayStream(new BitArray(new int[] { C }));
if (Cb.ReadBits(0, 1) == 3)
{
t[2] = 2;
t[1] = Cb.ReadBit(4);
t[0] = (Cb.ReadBit(3) << 1) | (Cb.ReadBit(2) & ~Cb.ReadBit(3));
}
else if (Cb.ReadBits(2, 3) == 3)
{
t[2] = 2;
t[1] = 2;
t[0] = Cb.ReadBits(0, 1);
}
else
{
t[2] = Cb.ReadBit(4);
t[1] = Cb.ReadBits(2, 3);
t[0] = (Cb.ReadBit(1) << 1) | (Cb.ReadBit(0) & ~Cb.ReadBit(1));
}
for (int i = 0; i < 5; i++)
{
IntegerEncoded IntEncoded = new IntegerEncoded(EIntegerEncoding.Trit, NumberBitsPerValue)
{
BitValue = m[i],
TritValue = t[i]
};
ListIntegerEncoded.Add(IntEncoded);
}
}
public static void DecodeQuintBlock(
BitArrayStream BitStream,
List<IntegerEncoded> ListIntegerEncoded,
int NumberBitsPerValue)
{
// Implement the algorithm in section C.2.12
int[] m = new int[3];
int[] q = new int[3];
int Q;
// Read the trit encoded block according to
// table C.2.15
m[0] = BitStream.ReadBits(NumberBitsPerValue);
Q = BitStream.ReadBits(3);
m[1] = BitStream.ReadBits(NumberBitsPerValue);
Q |= BitStream.ReadBits(2) << 3;
m[2] = BitStream.ReadBits(NumberBitsPerValue);
Q |= BitStream.ReadBits(2) << 5;
BitArrayStream Qb = new BitArrayStream(new BitArray(new int[] { Q }));
if (Qb.ReadBits(1, 2) == 3 && Qb.ReadBits(5, 6) == 0)
{
q[0] = q[1] = 4;
q[2] = (Qb.ReadBit(0) << 2) | ((Qb.ReadBit(4) & ~Qb.ReadBit(0)) << 1) | (Qb.ReadBit(3) & ~Qb.ReadBit(0));
}
else
{
int C = 0;
if (Qb.ReadBits(1, 2) == 3)
{
q[2] = 4;
C = (Qb.ReadBits(3, 4) << 3) | ((~Qb.ReadBits(5, 6) & 3) << 1) | Qb.ReadBit(0);
}
else
{
q[2] = Qb.ReadBits(5, 6);
C = Qb.ReadBits(0, 4);
}
BitArrayStream Cb = new BitArrayStream(new BitArray(new int[] { C }));
if (Cb.ReadBits(0, 2) == 5)
{
q[1] = 4;
q[0] = Cb.ReadBits(3, 4);
}
else
{
q[1] = Cb.ReadBits(3, 4);
q[0] = Cb.ReadBits(0, 2);
}
}
for (int i = 0; i < 3; i++)
{
IntegerEncoded IntEncoded = new IntegerEncoded(EIntegerEncoding.Quint, NumberBitsPerValue)
{
BitValue = m[i],
QuintValue = q[i]
};
ListIntegerEncoded.Add(IntEncoded);
}
}
public static void DecodeIntegerSequence(
List<IntegerEncoded> DecodeIntegerSequence,
BitArrayStream BitStream,
int MaxRange,
int NumberValues)
{
// Determine encoding parameters
IntegerEncoded IntEncoded = CreateEncoding(MaxRange);
// Start decoding
int NumberValuesDecoded = 0;
while (NumberValuesDecoded < NumberValues)
{
switch (IntEncoded.GetEncoding())
{
case EIntegerEncoding.Quint:
{
DecodeQuintBlock(BitStream, DecodeIntegerSequence, IntEncoded.NumberBits);
NumberValuesDecoded += 3;
break;
}
case EIntegerEncoding.Trit:
{
DecodeTritBlock(BitStream, DecodeIntegerSequence, IntEncoded.NumberBits);
NumberValuesDecoded += 5;
break;
}
case EIntegerEncoding.JustBits:
{
IntEncoded.BitValue = BitStream.ReadBits(IntEncoded.NumberBits);
DecodeIntegerSequence.Add(IntEncoded);
NumberValuesDecoded++;
break;
}
}
}
}
}
}

View file

@ -0,0 +1,19 @@
namespace Ryujinx.Graphics.Texture
{
class LinearSwizzle : ISwizzle
{
private int Pitch;
private int Bpp;
public LinearSwizzle(int Pitch, int Bpp)
{
this.Pitch = Pitch;
this.Bpp = Bpp;
}
public int GetSwizzleOffset(int X, int Y)
{
return X * Bpp + Y * Pitch;
}
}
}

View file

@ -0,0 +1,117 @@
using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Memory;
using System;
namespace Ryujinx.Graphics.Texture
{
static class TextureFactory
{
public static GalImage MakeTexture(NvGpuVmm Vmm, long TicPosition)
{
int[] Tic = ReadWords(Vmm, TicPosition, 8);
GalImageFormat Format = GetImageFormat(Tic);
GalTextureSource XSource = (GalTextureSource)((Tic[0] >> 19) & 7);
GalTextureSource YSource = (GalTextureSource)((Tic[0] >> 22) & 7);
GalTextureSource ZSource = (GalTextureSource)((Tic[0] >> 25) & 7);
GalTextureSource WSource = (GalTextureSource)((Tic[0] >> 28) & 7);
TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7);
GalMemoryLayout Layout;
if (Swizzle == TextureSwizzle.BlockLinear ||
Swizzle == TextureSwizzle.BlockLinearColorKey)
{
Layout = GalMemoryLayout.BlockLinear;
}
else
{
Layout = GalMemoryLayout.Pitch;
}
int BlockHeightLog2 = (Tic[3] >> 3) & 7;
int TileWidthLog2 = (Tic[3] >> 10) & 7;
int BlockHeight = 1 << BlockHeightLog2;
int TileWidth = 1 << TileWidthLog2;
int Width = (Tic[4] & 0xffff) + 1;
int Height = (Tic[5] & 0xffff) + 1;
GalImage Image = new GalImage(
Width,
Height,
TileWidth,
BlockHeight,
Layout,
Format,
XSource,
YSource,
ZSource,
WSource);
if (Layout == GalMemoryLayout.Pitch)
{
Image.Pitch = (Tic[3] & 0xffff) << 5;
}
return Image;
}
public static GalTextureSampler MakeSampler(NvGpu Gpu, NvGpuVmm Vmm, long TscPosition)
{
int[] Tsc = ReadWords(Vmm, TscPosition, 8);
GalTextureWrap AddressU = (GalTextureWrap)((Tsc[0] >> 0) & 7);
GalTextureWrap AddressV = (GalTextureWrap)((Tsc[0] >> 3) & 7);
GalTextureWrap AddressP = (GalTextureWrap)((Tsc[0] >> 6) & 7);
GalTextureFilter MagFilter = (GalTextureFilter) ((Tsc[1] >> 0) & 3);
GalTextureFilter MinFilter = (GalTextureFilter) ((Tsc[1] >> 4) & 3);
GalTextureMipFilter MipFilter = (GalTextureMipFilter)((Tsc[1] >> 6) & 3);
GalColorF BorderColor = new GalColorF(
BitConverter.Int32BitsToSingle(Tsc[4]),
BitConverter.Int32BitsToSingle(Tsc[5]),
BitConverter.Int32BitsToSingle(Tsc[6]),
BitConverter.Int32BitsToSingle(Tsc[7]));
return new GalTextureSampler(
AddressU,
AddressV,
AddressP,
MinFilter,
MagFilter,
MipFilter,
BorderColor);
}
private static GalImageFormat GetImageFormat(int[] Tic)
{
GalTextureType RType = (GalTextureType)((Tic[0] >> 7) & 7);
GalTextureType GType = (GalTextureType)((Tic[0] >> 10) & 7);
GalTextureType BType = (GalTextureType)((Tic[0] >> 13) & 7);
GalTextureType AType = (GalTextureType)((Tic[0] >> 16) & 7);
GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f);
bool ConvSrgb = ((Tic[4] >> 22) & 1) != 0;
return ImageUtils.ConvertTexture(Format, RType, GType, BType, AType, ConvSrgb);
}
private static int[] ReadWords(NvGpuVmm Vmm, long Position, int Count)
{
int[] Words = new int[Count];
for (int Index = 0; Index < Count; Index++, Position += 4)
{
Words[Index] = Vmm.ReadInt32(Position);
}
return Words;
}
}
}

View file

@ -0,0 +1,42 @@
using ChocolArm64.Memory;
using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Memory;
namespace Ryujinx.Graphics.Texture
{
static class TextureHelper
{
public static ISwizzle GetSwizzle(GalImage Image)
{
int BlockWidth = ImageUtils.GetBlockWidth (Image.Format);
int BytesPerPixel = ImageUtils.GetBytesPerPixel(Image.Format);
int Width = (Image.Width + (BlockWidth - 1)) / BlockWidth;
if (Image.Layout == GalMemoryLayout.BlockLinear)
{
int AlignMask = Image.TileWidth * (64 / BytesPerPixel) - 1;
Width = (Width + AlignMask) & ~AlignMask;
return new BlockLinearSwizzle(Width, BytesPerPixel, Image.GobBlockHeight);
}
else
{
return new LinearSwizzle(Image.Pitch, BytesPerPixel);
}
}
public static (MemoryManager Memory, long Position) GetMemoryAndPosition(
IMemory Memory,
long Position)
{
if (Memory is NvGpuVmm Vmm)
{
return (Vmm.Memory, Vmm.GetPhysicalAddress(Position));
}
return ((MemoryManager)Memory, Position);
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Texture
{
public enum TextureSwizzle
{
_1dBuffer = 0,
PitchColorKey = 1,
Pitch = 2,
BlockLinear = 3,
BlockLinearColorKey = 4
}
}