Added support for more shader instructions and texture formats, fix swapped channels in RGB565 and RGBA5551? texture formats, allow zero values on blending registers, initial work to build CFG on the shader decoder, update the BRA instruction to work with it (WIP)

This commit is contained in:
gdkchan 2018-05-29 20:37:10 -03:00
parent 9670c096e4
commit f43dd08064
14 changed files with 749 additions and 154 deletions

View file

@ -5,7 +5,7 @@ namespace ChocolArm64.Decoder
class ABlock
{
public long Position { get; set; }
public long EndPosition { get; set; }
public long EndPosition { get; set; }
public ABlock Next { get; set; }
public ABlock Branch { get; set; }

View file

@ -94,7 +94,7 @@ namespace ChocolArm64.Decoder
}
}
//If we have on the tree two blocks with the same end position,
//If we have on the graph two blocks with the same end position,
//then we need to split the bigger block and have two small blocks,
//the end position of the bigger "Current" block should then be == to
//the position of the "Smaller" block.

View file

@ -10,19 +10,132 @@ namespace Ryujinx.Core.Gpu
{
switch (Texture.Format)
{
case GalTextureFormat.A8B8G8R8: return Read4Bpp (Memory, Texture);
case GalTextureFormat.A1B5G5R5: return Read2Bpp (Memory, Texture);
case GalTextureFormat.B5G6R5: return Read2Bpp (Memory, Texture);
case GalTextureFormat.BC1: return Read8Bpt4x4 (Memory, Texture);
case GalTextureFormat.BC2: return Read16Bpt4x4(Memory, Texture);
case GalTextureFormat.BC3: return Read16Bpt4x4(Memory, Texture);
case GalTextureFormat.BC4: return Read8Bpt4x4 (Memory, Texture);
case GalTextureFormat.BC5: return Read16Bpt4x4(Memory, Texture);
case GalTextureFormat.R32G32B32A32: return Read16Bpp (Memory, Texture);
case GalTextureFormat.R16G16B16A16: return Read8Bpp (Memory, Texture);
case GalTextureFormat.A8B8G8R8: return Read4Bpp (Memory, Texture);
case GalTextureFormat.R32: return Read4Bpp (Memory, Texture);
case GalTextureFormat.A1B5G5R5: return Read5551 (Memory, Texture);
case GalTextureFormat.B5G6R5: return Read565 (Memory, Texture);
case GalTextureFormat.G8R8: return Read2Bpp (Memory, Texture);
case GalTextureFormat.R8: return Read1Bpp (Memory, Texture);
case GalTextureFormat.BC1: return Read8Bpt4x4 (Memory, Texture);
case GalTextureFormat.BC2: return Read16Bpt4x4(Memory, Texture);
case GalTextureFormat.BC3: return Read16Bpt4x4(Memory, Texture);
case GalTextureFormat.BC4: return Read8Bpt4x4 (Memory, Texture);
case GalTextureFormat.BC5: return Read16Bpt4x4(Memory, Texture);
}
throw new NotImplementedException(Texture.Format.ToString());
}
private unsafe static byte[] Read1Bpp(IAMemory Memory, Texture Texture)
{
int Width = Texture.Width;
int Height = Texture.Height;
byte[] Output = new byte[Width * Height];
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 1);
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
Memory,
Texture.Position);
fixed (byte* BuffPtr = Output)
{
long OutOffs = 0;
for (int Y = 0; Y < Height; Y++)
for (int X = 0; X < Width; X++)
{
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
byte Pixel = CpuMem.ReadByteUnchecked(Position + Offset);
*(BuffPtr + OutOffs) = Pixel;
OutOffs++;
}
}
return Output;
}
private unsafe static byte[] Read5551(IAMemory Memory, Texture Texture)
{
int Width = Texture.Width;
int Height = Texture.Height;
byte[] Output = new byte[Width * Height * 2];
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
Memory,
Texture.Position);
fixed (byte* BuffPtr = Output)
{
long OutOffs = 0;
for (int Y = 0; Y < Height; Y++)
for (int X = 0; X < Width; X++)
{
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset);
Pixel = (Pixel & 0x001f) << 11 |
(Pixel & 0x03e0) << 1 |
(Pixel & 0x7c00) >> 9 |
(Pixel & 0x8000) >> 15;
*(short*)(BuffPtr + OutOffs) = (short)Pixel;
OutOffs += 2;
}
}
return Output;
}
private unsafe static byte[] Read565(IAMemory Memory, Texture Texture)
{
int Width = Texture.Width;
int Height = Texture.Height;
byte[] Output = new byte[Width * Height * 2];
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
Memory,
Texture.Position);
fixed (byte* BuffPtr = Output)
{
long OutOffs = 0;
for (int Y = 0; Y < Height; Y++)
for (int X = 0; X < Width; X++)
{
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset);
Pixel = (Pixel & 0x001f) << 11 |
(Pixel & 0x07e0) |
(Pixel & 0xf800) >> 11;
*(short*)(BuffPtr + OutOffs) = (short)Pixel;
OutOffs += 2;
}
}
return Output;
}
private unsafe static byte[] Read2Bpp(IAMemory Memory, Texture Texture)
{
int Width = Texture.Width;
@ -89,6 +202,74 @@ namespace Ryujinx.Core.Gpu
return Output;
}
private unsafe static byte[] Read8Bpp(IAMemory Memory, Texture Texture)
{
int Width = Texture.Width;
int Height = Texture.Height;
byte[] Output = new byte[Width * Height * 8];
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8);
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
Memory,
Texture.Position);
fixed (byte* BuffPtr = Output)
{
long OutOffs = 0;
for (int Y = 0; Y < Height; Y++)
for (int X = 0; X < Width; X++)
{
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
long Pixel = CpuMem.ReadInt64Unchecked(Position + Offset);
*(long*)(BuffPtr + OutOffs) = Pixel;
OutOffs += 8;
}
}
return Output;
}
private unsafe static byte[] Read16Bpp(IAMemory Memory, Texture Texture)
{
int Width = Texture.Width;
int Height = Texture.Height;
byte[] Output = new byte[Width * Height * 16];
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16);
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
Memory,
Texture.Position);
fixed (byte* BuffPtr = Output)
{
long OutOffs = 0;
for (int Y = 0; Y < Height; Y++)
for (int X = 0; X < Width; X++)
{
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
long PxLow = CpuMem.ReadInt64Unchecked(Position + Offset + 0);
long PxHigh = CpuMem.ReadInt64Unchecked(Position + Offset + 8);
*(long*)(BuffPtr + OutOffs + 0) = PxLow;
*(long*)(BuffPtr + OutOffs + 8) = PxHigh;
OutOffs += 16;
}
}
return Output;
}
private unsafe static byte[] Read8Bpt4x4(IAMemory Memory, Texture Texture)
{
int Width = (Texture.Width + 3) / 4;

View file

@ -2,13 +2,18 @@ namespace Ryujinx.Graphics.Gal
{
public enum GalTextureFormat
{
A8B8G8R8 = 0x8,
A1B5G5R5 = 0x14,
B5G6R5 = 0x15,
BC1 = 0x24,
BC2 = 0x25,
BC3 = 0x26,
BC4 = 0x27,
BC5 = 0x28
R32G32B32A32 = 0x1,
R16G16B16A16 = 0x3,
A8B8G8R8 = 0x8,
R32 = 0xf,
A1B5G5R5 = 0x14,
B5G6R5 = 0x15,
G8R8 = 0x18,
R8 = 0x1d,
BC1 = 0x24,
BC2 = 0x25,
BC3 = 0x26,
BC4 = 0x27,
BC5 = 0x28
}
}

View file

@ -59,9 +59,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
switch (Format)
{
case GalTextureFormat.A8B8G8R8: return (PixelFormat.Rgba, PixelType.UnsignedByte);
case GalTextureFormat.A1B5G5R5: return (PixelFormat.Rgba, PixelType.UnsignedShort5551);
case GalTextureFormat.B5G6R5: return (PixelFormat.Rgb, PixelType.UnsignedShort565);
case GalTextureFormat.R32G32B32A32: return (PixelFormat.Rgba, PixelType.Float);
case GalTextureFormat.R16G16B16A16: return (PixelFormat.Rgba, PixelType.HalfFloat);
case GalTextureFormat.A8B8G8R8: return (PixelFormat.Rgba, PixelType.UnsignedByte);
case GalTextureFormat.R32: return (PixelFormat.Red, PixelType.Float);
case GalTextureFormat.A1B5G5R5: return (PixelFormat.Rgba, PixelType.UnsignedShort5551);
case GalTextureFormat.B5G6R5: return (PixelFormat.Rgb, PixelType.UnsignedShort565);
case GalTextureFormat.G8R8: return (PixelFormat.Rg, PixelType.UnsignedByte);
case GalTextureFormat.R8: return (PixelFormat.Red, PixelType.UnsignedByte);
}
throw new NotImplementedException(Format.ToString());
@ -145,20 +150,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
switch (BlendEquation)
{
default:
case GalBlendEquation.FuncAdd: return BlendEquationMode.FuncAdd;
case GalBlendEquation.FuncSubtract: return BlendEquationMode.FuncSubtract;
case GalBlendEquation.FuncReverseSubtract: return BlendEquationMode.FuncReverseSubtract;
case GalBlendEquation.Min: return BlendEquationMode.Min;
case GalBlendEquation.Max: return BlendEquationMode.Max;
}
throw new ArgumentException(nameof(BlendEquation));
}
public static BlendingFactorSrc GetBlendFactorSrc(GalBlendFactor BlendFactor)
{
switch (BlendFactor)
{
default:
case GalBlendFactor.Zero: return BlendingFactorSrc.Zero;
case GalBlendFactor.One: return BlendingFactorSrc.One;
case GalBlendFactor.SrcColor: return BlendingFactorSrc.SrcColor;
@ -179,14 +184,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL
case GalBlendFactor.Src1Alpha: return BlendingFactorSrc.Src1Alpha;
case GalBlendFactor.OneMinusSrc1Alpha: return BlendingFactorSrc.OneMinusSrc1Alpha;
}
throw new ArgumentException(nameof(BlendFactor));
}
public static BlendingFactorDest GetBlendFactorDst(GalBlendFactor BlendFactor)
{
switch (BlendFactor)
{
default:
case GalBlendFactor.Zero: return BlendingFactorDest.Zero;
case GalBlendFactor.One: return BlendingFactorDest.One;
case GalBlendFactor.SrcColor: return BlendingFactorDest.SrcColor;
@ -207,8 +211,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
case GalBlendFactor.Src1Alpha: return BlendingFactorDest.Src1Alpha;
case GalBlendFactor.OneMinusSrc1Alpha: return BlendingFactorDest.OneMinusSrc1Alpha;
}
throw new ArgumentException(nameof(BlendFactor));
}
}
}

View file

@ -4,6 +4,10 @@ namespace Ryujinx.Graphics.Gal.Shader
{
class GlslDecl
{
public const int TessCoordAttrX = 0x2f0;
public const int TessCoordAttrY = 0x2f4;
public const int TessCoordAttrZ = 0x2f8;
public const int InstanceIdAttr = 0x2f8;
public const int VertexIdAttr = 0x2fc;
public const int GlPositionWAttr = 0x7c;
@ -48,7 +52,7 @@ namespace Ryujinx.Graphics.Gal.Shader
public GalShaderType ShaderType { get; private set; }
public GlslDecl(ShaderIrNode[] Nodes, GalShaderType ShaderType)
public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType)
{
this.ShaderType = ShaderType;
@ -75,9 +79,12 @@ namespace Ryujinx.Graphics.Gal.Shader
m_OutAttributes.Add(7, new ShaderDeclInfo("gl_Position", -1, 0, 4));
}
foreach (ShaderIrNode Node in Nodes)
foreach (ShaderIrBlock Block in Blocks)
{
Traverse(null, Node);
foreach (ShaderIrNode Node in Block.GetNodes())
{
Traverse(null, Node);
}
}
}

View file

@ -25,6 +25,8 @@ namespace Ryujinx.Graphics.Gal.Shader
private GlslDecl Decl;
private ShaderIrBlock[] Blocks;
private StringBuilder SB;
public GlslDecompiler()
@ -37,6 +39,8 @@ namespace Ryujinx.Graphics.Gal.Shader
{ ShaderIrInst.Asr, GetAsrExpr },
{ ShaderIrInst.Band, GetBandExpr },
{ ShaderIrInst.Bnot, GetBnotExpr },
{ ShaderIrInst.Bor, GetBorExpr },
{ ShaderIrInst.Bxor, GetBxorExpr },
{ ShaderIrInst.Ceil, GetCeilExpr },
{ ShaderIrInst.Ceq, GetCeqExpr },
{ ShaderIrInst.Cge, GetCgeExpr },
@ -50,19 +54,27 @@ namespace Ryujinx.Graphics.Gal.Shader
{ ShaderIrInst.Fabs, GetAbsExpr },
{ ShaderIrInst.Fadd, GetAddExpr },
{ ShaderIrInst.Fceq, GetCeqExpr },
{ ShaderIrInst.Fcequ, GetCequExpr },
{ ShaderIrInst.Fcge, GetCgeExpr },
{ ShaderIrInst.Fcgeu, GetCgeuExpr },
{ ShaderIrInst.Fcgt, GetCgtExpr },
{ ShaderIrInst.Fcgtu, GetCgtuExpr },
{ ShaderIrInst.Fclamp, GetFclampExpr },
{ ShaderIrInst.Fcle, GetCleExpr },
{ ShaderIrInst.Fcleu, GetCleuExpr },
{ ShaderIrInst.Fclt, GetCltExpr },
{ ShaderIrInst.Fcltu, GetCltuExpr },
{ ShaderIrInst.Fcnan, GetCnanExpr },
{ ShaderIrInst.Fcne, GetCneExpr },
{ ShaderIrInst.Fcneu, GetCneuExpr },
{ ShaderIrInst.Fcnum, GetCnumExpr },
{ ShaderIrInst.Fcos, GetFcosExpr },
{ ShaderIrInst.Fex2, GetFex2Expr },
{ ShaderIrInst.Ffma, GetFfmaExpr },
{ ShaderIrInst.Flg2, GetFlg2Expr },
{ ShaderIrInst.Floor, GetFloorExpr },
{ ShaderIrInst.Fmax, GetFmaxExpr },
{ ShaderIrInst.Fmin, GetFminExpr },
{ ShaderIrInst.Fmax, GetMaxExpr },
{ ShaderIrInst.Fmin, GetMinExpr },
{ ShaderIrInst.Fmul, GetMulExpr },
{ ShaderIrInst.Fneg, GetNegExpr },
{ ShaderIrInst.Frcp, GetFrcpExpr },
@ -74,6 +86,8 @@ namespace Ryujinx.Graphics.Gal.Shader
{ ShaderIrInst.Kil, GetKilExpr },
{ ShaderIrInst.Lsl, GetLslExpr },
{ ShaderIrInst.Lsr, GetLsrExpr },
{ ShaderIrInst.Max, GetMaxExpr },
{ ShaderIrInst.Min, GetMinExpr },
{ ShaderIrInst.Mul, GetMulExpr },
{ ShaderIrInst.Neg, GetNegExpr },
{ ShaderIrInst.Not, GetNotExpr },
@ -91,11 +105,9 @@ namespace Ryujinx.Graphics.Gal.Shader
public GlslProgram Decompile(IGalMemory Memory, long Position, GalShaderType ShaderType)
{
ShaderIrBlock Block = ShaderDecoder.DecodeBasicBlock(Memory, Position);
Blocks = ShaderDecoder.Decode(Memory, Position);
ShaderIrNode[] Nodes = Block.GetNodes();
Decl = new GlslDecl(Nodes, ShaderType);
Decl = new GlslDecl(Blocks, ShaderType);
SB = new StringBuilder();
@ -108,7 +120,7 @@ namespace Ryujinx.Graphics.Gal.Shader
PrintDeclGprs();
PrintDeclPreds();
PrintBlockScope(Nodes, 0, Nodes.Length, "void main()", 1);
PrintBlockScope(Blocks[0], null, null, "void main()", IdentationStr);
string GlslCode = SB.ToString();
@ -230,37 +242,75 @@ namespace Ryujinx.Graphics.Gal.Shader
}
private void PrintBlockScope(
ShaderIrNode[] Nodes,
int Start,
int Count,
string ScopeName,
int IdentationLevel)
ShaderIrBlock Block,
ShaderIrBlock EndBlock,
ShaderIrBlock LoopBlock,
string ScopeName,
string Identation,
bool IsDoWhile = false)
{
string Identation = string.Empty;
string UpIdent = Identation.Substring(0, Identation.Length - IdentationStr.Length);
for (int Index = 0; Index < IdentationLevel - 1; Index++)
if (IsDoWhile)
{
Identation += IdentationStr;
SB.AppendLine(UpIdent + "do {");
}
else
{
SB.AppendLine(UpIdent + ScopeName + " {");
}
if (ScopeName != string.Empty)
while (Block != null && Block != EndBlock)
{
ScopeName += " ";
ShaderIrNode[] Nodes = Block.GetNodes();
Block = PrintNodes(Block, EndBlock, LoopBlock, Identation, Nodes);
}
SB.AppendLine(Identation + ScopeName + "{");
string LastLine = Identation + "}";
if (IdentationLevel > 0)
if (IsDoWhile)
{
Identation += IdentationStr;
SB.AppendLine(UpIdent + "} " + ScopeName + ";");
}
else
{
SB.AppendLine(UpIdent + "}");
}
}
private ShaderIrBlock PrintNodes(
ShaderIrBlock Block,
ShaderIrBlock EndBlock,
ShaderIrBlock LoopBlock,
string Identation,
params ShaderIrNode[] Nodes)
{
/*
* Notes about control flow and if-else/loop generation:
* The code assumes that the program has sane control flow,
* that is, there's no jumps to a location after another jump or
* jump target (except for the end of an if-else block), and backwards
* jumps to a location before the last loop dominator.
* Such cases needs to be transformed on a step before the GLSL code
* generation to ensure that we have sane graphs to work with.
* TODO: Such transformation is not yet implemented.
*/
string NewIdent = Identation + IdentationStr;
ShaderIrBlock LoopTail = GetLoopTailBlock(Block);
if (LoopTail != null && LoopBlock != Block)
{
//Shoock! kuma shock! We have a loop here!
//The entire sequence needs to be inside a do-while block.
ShaderIrBlock LoopEnd = GetDownBlock(LoopTail);
PrintBlockScope(Block, LoopEnd, Block, "while (false)", NewIdent, IsDoWhile: true);
return LoopEnd;
}
for (int Index = Start; Index < Start + Count; Index++)
foreach (ShaderIrNode Node in Nodes)
{
ShaderIrNode Node = Nodes[Index];
if (Node is ShaderIrCond Cond)
{
string IfExpr = GetSrcExpr(Cond.Pred, true);
@ -272,42 +322,41 @@ namespace Ryujinx.Graphics.Gal.Shader
if (Cond.Child is ShaderIrOp Op && Op.Inst == ShaderIrInst.Bra)
{
ShaderIrLabel Label = (ShaderIrLabel)Op.OperandA;
//Branch is a loop branch and would result in infinite recursion.
if (Block.Branch.Position <= Block.Position)
{
SB.AppendLine(Identation + "if (" + IfExpr + ") {");
int Target = FindLabel(Nodes, Label, Index + 1);
SB.AppendLine(Identation + IdentationStr + "continue;");
int IfCount = Target - Index - 1;
SB.AppendLine(Identation + "}");
continue;
}
string SubScopeName = "if (!" + IfExpr + ")";
if (Nodes[Index + IfCount] is ShaderIrOp LastOp && LastOp.Inst == ShaderIrInst.Bra)
PrintBlockScope(Block.Next, Block.Branch, LoopBlock, SubScopeName, NewIdent);
ShaderIrBlock IfElseEnd = GetUpBlock(Block.Branch).Branch;
if (IfElseEnd?.Position > Block.Branch.Position)
{
Target = FindLabel(Nodes, (ShaderIrLabel)LastOp.OperandA, Index + 1);
PrintBlockScope(Block.Branch, IfElseEnd, LoopBlock, "else", NewIdent);
int ElseCount = Target - (Index + 1 + IfCount);
PrintBlockScope(Nodes, Index + 1, IfCount - 1, SubScopeName, IdentationLevel + 1);
PrintBlockScope(Nodes, Index + 1 + IfCount, ElseCount, "else", IdentationLevel + 1);
Index += IfCount + ElseCount;
return IfElseEnd;
}
else
{
PrintBlockScope(Nodes, Index + 1, IfCount, SubScopeName, IdentationLevel + 1);
Index += IfCount;
}
return Block.Branch;
}
else
{
string SubScopeName = "if (" + IfExpr + ")";
SB.AppendLine(Identation + "if (" + IfExpr + ") {");
ShaderIrNode[] Child = new ShaderIrNode[] { Cond.Child };
PrintNodes(Block, EndBlock, LoopBlock, NewIdent, Cond.Child);
PrintBlockScope(Child, 0, 1, SubScopeName, IdentationLevel + 1);
SB.AppendLine(Identation + "}");
}
}
else if (Node is ShaderIrAsg Asg)
{
@ -322,7 +371,16 @@ namespace Ryujinx.Graphics.Gal.Shader
}
else if (Node is ShaderIrOp Op)
{
if (Op.Inst == ShaderIrInst.Exit)
if (Op.Inst == ShaderIrInst.Bra)
{
if (Block.Branch.Position <= Block.Position)
{
SB.AppendLine(Identation + "continue;");
}
continue;
}
else if (Op.Inst == ShaderIrInst.Exit)
{
//Do everything that needs to be done before
//the shader ends here.
@ -336,10 +394,6 @@ namespace Ryujinx.Graphics.Gal.Shader
SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";");
}
else if (Node is ShaderIrLabel Label)
{
//TODO: Add support for loops here.
}
else if (Node is ShaderIrCmnt Cmnt)
{
SB.AppendLine(Identation + "// " + Cmnt.Comment);
@ -350,22 +404,35 @@ namespace Ryujinx.Graphics.Gal.Shader
}
}
SB.AppendLine(LastLine);
return Block.Next;
}
private int FindLabel(ShaderIrNode[] Nodes, ShaderIrLabel Label, int Start)
private ShaderIrBlock GetUpBlock(ShaderIrBlock Block)
{
int Target;
return Blocks.FirstOrDefault(x => x.EndPosition == Block.Position);
}
for (Target = Start; Target < Nodes.Length; Target++)
private ShaderIrBlock GetDownBlock(ShaderIrBlock Block)
{
return Blocks.FirstOrDefault(x => x.Position == Block.EndPosition);
}
private ShaderIrBlock GetLoopTailBlock(ShaderIrBlock LoopHead)
{
ShaderIrBlock Tail = null;
foreach (ShaderIrBlock Block in LoopHead.Sources)
{
if (Nodes[Target] == Label)
if (Block.Position >= LoopHead.Position)
{
return Target;
if (Tail == null || Tail.Position < Block.Position)
{
Tail = Block;
}
}
}
throw new InvalidOperationException();
return Tail;
}
private bool IsValidOutOper(ShaderIrNode Node)
@ -480,9 +547,22 @@ namespace Ryujinx.Graphics.Gal.Shader
private string GetName(ShaderIrOperAbuf Abuf)
{
if (Abuf.Offs == GlslDecl.VertexIdAttr)
if (Decl.ShaderType == GalShaderType.Vertex)
{
return "gl_VertexID";
switch (Abuf.Offs)
{
case GlslDecl.VertexIdAttr: return "gl_VertexID";
case GlslDecl.InstanceIdAttr: return "gl_InstanceID";
}
}
else if (Decl.ShaderType == GalShaderType.TessEvaluation)
{
switch (Abuf.Offs)
{
case GlslDecl.TessCoordAttrX: return "gl_TessCoord.x";
case GlslDecl.TessCoordAttrY: return "gl_TessCoord.y";
case GlslDecl.TessCoordAttrZ: return "gl_TessCoord.z";
}
}
return GetName(Decl.InAttributes, Abuf);
@ -567,6 +647,10 @@ namespace Ryujinx.Graphics.Gal.Shader
private string GetBnotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "!");
private string GetBorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "||");
private string GetBxorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^^");
private string GetCeilExpr(ShaderIrOp Op) => GetUnaryCall(Op, "ceil");
private string GetClampsExpr(ShaderIrOp Op)
@ -583,13 +667,34 @@ namespace Ryujinx.Graphics.Gal.Shader
"uint(" + GetOperExpr(Op, Op.OperandC) + ")))";
}
private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<");
private string GetCeqExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "==");
private string GetCleExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<=");
private string GetCgtExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">");
private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!=");
private string GetCequExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "==");
private string GetCgeExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">=");
private string GetCgeuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, ">=");
private string GetCgtExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">");
private string GetCgtuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, ">");
private string GetCleExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<=");
private string GetCleuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "<=");
private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<");
private string GetCltuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "<");
private string GetCnanExpr(ShaderIrOp Op) => GetUnaryCall(Op, "isnan");
private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!=");
private string GetCneuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "!=");
private string GetCnumExpr(ShaderIrOp Op) => GetUnaryCall(Op, "!isnan");
private string GetExitExpr(ShaderIrOp Op) => "return";
private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos");
@ -604,9 +709,6 @@ namespace Ryujinx.Graphics.Gal.Shader
private string GetFloorExpr(ShaderIrOp Op) => GetUnaryCall(Op, "floor");
private string GetFmaxExpr(ShaderIrOp Op) => GetBinaryCall(Op, "max");
private string GetFminExpr(ShaderIrOp Op) => GetBinaryCall(Op, "min");
private string GetFrcpExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "1 / ");
private string GetFrsqExpr(ShaderIrOp Op) => GetUnaryCall(Op, "inversesqrt");
@ -634,6 +736,9 @@ namespace Ryujinx.Graphics.Gal.Shader
GetOperExpr(Op, Op.OperandB) + ")";
}
private string GetMaxExpr(ShaderIrOp Op) => GetBinaryCall(Op, "max");
private string GetMinExpr(ShaderIrOp Op) => GetBinaryCall(Op, "min");
private string GetMulExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "*");
private string GetNegExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "-");
@ -733,6 +838,18 @@ namespace Ryujinx.Graphics.Gal.Shader
GetOperExpr(Op, Op.OperandB);
}
private string GetBinaryExprWithNaN(ShaderIrOp Op, string Opr)
{
string A = GetOperExpr(Op, Op.OperandA);
string B = GetOperExpr(Op, Op.OperandB);
string NaNCheck =
" || isnan(" + A + ")" +
" || isnan(" + B + ")";
return A + " " + Opr + " " + B + NaNCheck;
}
private string GetTernaryExpr(ShaderIrOp Op, string Opr1, string Opr2)
{
return GetOperExpr(Op, Op.OperandA) + " " + Opr1 + " " +

View file

@ -126,6 +126,21 @@ namespace Ryujinx.Graphics.Gal.Shader
EmitFsetp(Block, OpCode, ShaderOper.RR);
}
public static void Imnmx_C(ShaderIrBlock Block, long OpCode)
{
EmitImnmx(Block, OpCode, ShaderOper.CR);
}
public static void Imnmx_I(ShaderIrBlock Block, long OpCode)
{
EmitImnmx(Block, OpCode, ShaderOper.Imm);
}
public static void Imnmx_R(ShaderIrBlock Block, long OpCode)
{
EmitImnmx(Block, OpCode, ShaderOper.RR);
}
public static void Ipa(ShaderIrBlock Block, long OpCode)
{
ShaderIrNode OperA = GetOperAbuf28(OpCode);
@ -265,6 +280,26 @@ namespace Ryujinx.Graphics.Gal.Shader
return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
}
public static void Xmad_CR(ShaderIrBlock Block, long OpCode)
{
EmitXmad(Block, OpCode, ShaderOper.CR);
}
public static void Xmad_I(ShaderIrBlock Block, long OpCode)
{
EmitXmad(Block, OpCode, ShaderOper.Imm);
}
public static void Xmad_RC(ShaderIrBlock Block, long OpCode)
{
EmitXmad(Block, OpCode, ShaderOper.RC);
}
public static void Xmad_RR(ShaderIrBlock Block, long OpCode)
{
EmitXmad(Block, OpCode, ShaderOper.RR);
}
private static void EmitAluBinary(
ShaderIrBlock Block,
long OpCode,
@ -411,6 +446,16 @@ namespace Ryujinx.Graphics.Gal.Shader
}
private static void EmitFmnmx(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
{
EmitMnmx(Block, OpCode, true, Oper);
}
private static void EmitImnmx(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
{
EmitMnmx(Block, OpCode, false, Oper);
}
private static void EmitMnmx(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper)
{
bool NegB = ((OpCode >> 45) & 1) != 0;
bool AbsA = ((OpCode >> 46) & 1) != 0;
@ -419,30 +464,48 @@ namespace Ryujinx.Graphics.Gal.Shader
ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
OperA = GetAluFabsFneg(OperA, AbsA, NegA);
if (IsFloat)
{
OperA = GetAluFabsFneg(OperA, AbsA, NegA);
}
else
{
OperA = GetAluIabsIneg(OperA, AbsA, NegA);
}
switch (Oper)
{
case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break;
case ShaderOper.Imm: OperB = GetOperImm19_20 (OpCode); break;
case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break;
default: throw new ArgumentException(nameof(Oper));
}
OperB = GetAluFabsFneg(OperB, AbsB, NegB);
if (IsFloat)
{
OperB = GetAluFabsFneg(OperB, AbsB, NegB);
}
else
{
OperB = GetAluIabsIneg(OperB, AbsB, NegB);
}
ShaderIrOperPred Pred = GetOperPred39(OpCode);
ShaderIrOp Op;
ShaderIrInst MaxInst = IsFloat ? ShaderIrInst.Fmax : ShaderIrInst.Max;
ShaderIrInst MinInst = IsFloat ? ShaderIrInst.Fmin : ShaderIrInst.Min;
if (Pred.IsConst)
{
bool IsMax = ((OpCode >> 42) & 1) != 0;
Op = new ShaderIrOp(IsMax
? ShaderIrInst.Fmax
: ShaderIrInst.Fmin, OperA, OperB);
? MaxInst
: MinInst, OperA, OperB);
Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
}
@ -450,8 +513,8 @@ namespace Ryujinx.Graphics.Gal.Shader
{
ShaderIrNode PredN = GetOperPred39N(OpCode);
ShaderIrOp OpMax = new ShaderIrOp(ShaderIrInst.Fmax, OperA, OperB);
ShaderIrOp OpMin = new ShaderIrOp(ShaderIrInst.Fmin, OperA, OperB);
ShaderIrOp OpMax = new ShaderIrOp(MaxInst, OperA, OperB);
ShaderIrOp OpMin = new ShaderIrOp(MinInst, OperA, OperB);
ShaderIrAsg AsgMax = new ShaderIrAsg(GetOperGpr0(OpCode), OpMax);
ShaderIrAsg AsgMin = new ShaderIrAsg(GetOperGpr0(OpCode), OpMin);
@ -646,5 +709,94 @@ namespace Ryujinx.Graphics.Gal.Shader
Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode));
}
private static void EmitXmad(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
{
//TODO: Confirm SignAB/C, it is just a guess.
//TODO: Implement Mode 3 (CSFU), what it does?
bool SignAB = ((OpCode >> 48) & 1) != 0;
bool SignC = ((OpCode >> 49) & 1) != 0;
bool HighB = ((OpCode >> 52) & 1) != 0;
bool HighA = ((OpCode >> 53) & 1) != 0;
int Mode = (int)(OpCode >> 50) & 7;
ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC;
ShaderIrOperImm Imm16 = new ShaderIrOperImm(16);
ShaderIrOperImm ImmMsk = new ShaderIrOperImm(0xffff);
ShaderIrInst ShiftAB = SignAB ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
ShaderIrInst ShiftC = SignC ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
if (HighA)
{
OperA = new ShaderIrOp(ShiftAB, OperA, Imm16);
}
switch (Oper)
{
case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break;
case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break;
case ShaderOper.RC: OperB = GetOperGpr39 (OpCode); break;
case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break;
default: throw new ArgumentException(nameof(Oper));
}
bool ProductShiftLeft = false, Merge = false;
if (Oper == ShaderOper.RC)
{
OperC = GetOperCbuf34(OpCode);
}
else
{
OperC = GetOperGpr39(OpCode);
ProductShiftLeft = ((OpCode >> 36) & 1) != 0;
Merge = ((OpCode >> 37) & 1) != 0;
}
switch (Mode)
{
//CLO.
case 1: OperC = ExtendTo32(OperC, SignC, 16); break;
//CHI.
case 2: OperC = new ShaderIrOp(ShiftC, OperC, Imm16); break;
}
ShaderIrNode OperBH = OperB;
if (HighB)
{
OperBH = new ShaderIrOp(ShiftAB, OperBH, Imm16);
}
ShaderIrOp MulOp = new ShaderIrOp(ShaderIrInst.Mul, OperA, OperBH);
if (ProductShiftLeft)
{
MulOp = new ShaderIrOp(ShaderIrInst.Lsl, MulOp, Imm16);
}
ShaderIrOp AddOp = new ShaderIrOp(ShaderIrInst.Add, MulOp, OperC);
if (Merge)
{
AddOp = new ShaderIrOp(ShaderIrInst.And, AddOp, ImmMsk);
OperB = new ShaderIrOp(ShaderIrInst.Lsl, OperB, Imm16);
AddOp = new ShaderIrOp(ShaderIrInst.Or, AddOp, OperB);
}
if (Mode == 4)
{
OperB = new ShaderIrOp(ShaderIrInst.Lsl, OperB, Imm16);
AddOp = new ShaderIrOp(ShaderIrInst.Or, AddOp, OperB);
}
Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), AddOp), OpCode));
}
}
}

View file

@ -15,11 +15,11 @@ namespace Ryujinx.Graphics.Gal.Shader
throw new NotImplementedException();
}
long Target = ((int)(OpCode >> 20) << 8) >> 8;
int Target = ((int)(OpCode >> 20) << 8) >> 8;
Target += Block.Position + 8;
ShaderIrOperImm Imm = new ShaderIrOperImm(Target);
Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Bra, Block.GetLabel(Target)), OpCode));
Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Bra, Imm), OpCode));
}
public static void Exit(ShaderIrBlock Block, long OpCode)

View file

@ -1,19 +1,133 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.Shader
{
static class ShaderDecoder
{
private const bool AddDbgComments = true;
public static ShaderIrBlock DecodeBasicBlock(IGalMemory Memory, long Position)
public static ShaderIrBlock[] Decode(IGalMemory Memory, long Start)
{
ShaderIrBlock Block = new ShaderIrBlock();
Dictionary<long, ShaderIrBlock> Visited = new Dictionary<long, ShaderIrBlock>();
Dictionary<long, ShaderIrBlock> VisitedEnd = new Dictionary<long, ShaderIrBlock>();
while (true)
Queue<ShaderIrBlock> Blocks = new Queue<ShaderIrBlock>();
ShaderIrBlock Enqueue(long Position, ShaderIrBlock Source = null)
{
Block.Position = Position;
if (!Visited.TryGetValue(Position, out ShaderIrBlock Output))
{
Output = new ShaderIrBlock(Position);
Block.MarkLabel(Position);
Blocks.Enqueue(Output);
Visited.Add(Position, Output);
}
if (Source != null)
{
Output.Sources.Add(Source);
}
return Output;
}
ShaderIrBlock Entry = Enqueue(Start);
while (Blocks.Count > 0)
{
ShaderIrBlock Current = Blocks.Dequeue();
FillBlock(Memory, Current);
//Set child blocks. "Branch" is the block the branch instruction
//points to (when taken), "Next" is the block at the next address,
//executed when the branch is not taken. For Unconditional Branches
//or end of shader, Next is null.
if (Current.Nodes.Count > 0)
{
ShaderIrNode LastNode = Current.GetLastNode();
ShaderIrOp Op = GetInnermostOp(LastNode);
if (Op?.Inst == ShaderIrInst.Bra)
{
int Offset = ((ShaderIrOperImm)Op.OperandA).Value;
long Target = Current.EndPosition + Offset;
Current.Branch = Enqueue(Target, Current);
}
if (NodeHasNext(LastNode))
{
Current.Next = Enqueue(Current.EndPosition);
}
}
//If we have on the graph two blocks with the same end position,
//then we need to split the bigger block and have two small blocks,
//the end position of the bigger "Current" block should then be == to
//the position of the "Smaller" block.
while (VisitedEnd.TryGetValue(Current.EndPosition, out ShaderIrBlock Smaller))
{
if (Current.Position > Smaller.Position)
{
ShaderIrBlock Temp = Smaller;
Smaller = Current;
Current = Temp;
}
Current.EndPosition = Smaller.Position;
Current.Next = Smaller;
Current.Branch = null;
Current.Nodes.RemoveRange(
Current.Nodes.Count - Smaller.Nodes.Count,
Smaller.Nodes.Count);
VisitedEnd[Smaller.EndPosition] = Smaller;
}
VisitedEnd.Add(Current.EndPosition, Current);
}
//Make and sort Graph blocks array by position.
ShaderIrBlock[] Graph = new ShaderIrBlock[Visited.Count];
while (Visited.Count > 0)
{
ulong FirstPos = ulong.MaxValue;
foreach (ShaderIrBlock Block in Visited.Values)
{
if (FirstPos > (ulong)Block.Position)
FirstPos = (ulong)Block.Position;
}
ShaderIrBlock Current = Visited[(long)FirstPos];
do
{
Graph[Graph.Length - Visited.Count] = Current;
Visited.Remove(Current.Position);
Current = Current.Next;
}
while (Current != null);
}
return Graph;
}
private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block)
{
long Position = Block.Position;
do
{
//Ignore scheduling instructions, which are written every 32 bytes.
if ((Position & 0x1f) == 0)
{
@ -33,9 +147,20 @@ namespace Ryujinx.Graphics.Gal.Shader
if (AddDbgComments)
{
string DbgOpCode = $"0x{Position:x16}: 0x{OpCode:x16} ";
string DbgOpCode = $"0x{(Position - 8):x16}: 0x{OpCode:x16} ";
Block.AddNode(new ShaderIrCmnt(DbgOpCode + (Decode?.Method.Name ?? "???")));
DbgOpCode += (Decode?.Method.Name ?? "???");
if (Decode == ShaderDecode.Bra)
{
int Offset = ((int)(OpCode >> 20) << 8) >> 8;
long Target = Position + Offset;
DbgOpCode += " (0x" + Target.ToString("x16") + ")";
}
Block.AddNode(new ShaderIrCmnt(DbgOpCode));
}
if (Decode == null)
@ -44,19 +169,36 @@ namespace Ryujinx.Graphics.Gal.Shader
}
Decode(Block, OpCode);
if (Block.GetLastNode() is ShaderIrOp Op && Op.Inst == ShaderIrInst.Exit)
{
break;
}
}
while (!IsFlowChange(Block.GetLastNode()));
return Block;
Block.EndPosition = Position;
}
private static bool IsFlowChange(ShaderIrInst Inst)
private static bool IsFlowChange(ShaderIrNode Node)
{
return Inst == ShaderIrInst.Exit;
return !NodeHasNext(GetInnermostOp(Node));
}
private static ShaderIrOp GetInnermostOp(ShaderIrNode Node)
{
if (Node is ShaderIrCond Cond)
{
Node = Cond.Child;
}
return Node is ShaderIrOp Op ? Op : null;
}
private static bool NodeHasNext(ShaderIrNode Node)
{
if (!(Node is ShaderIrOp Op))
{
return true;
}
return Op.Inst != ShaderIrInst.Exit &&
Op.Inst != ShaderIrInst.Bra;
}
}
}

View file

@ -4,17 +4,23 @@ namespace Ryujinx.Graphics.Gal.Shader
{
class ShaderIrBlock
{
private List<ShaderIrNode> Nodes;
public long Position { get; set; }
public long EndPosition { get; set; }
private Dictionary<long, ShaderIrLabel> LabelsToInsert;
public ShaderIrBlock Next { get; set; }
public ShaderIrBlock Branch { get; set; }
public long Position;
public List<ShaderIrBlock> Sources { get; private set; }
public ShaderIrBlock()
public List<ShaderIrNode> Nodes { get; private set; }
public ShaderIrBlock(long Position)
{
Nodes = new List<ShaderIrNode>();
this.Position = Position;
LabelsToInsert = new Dictionary<long, ShaderIrLabel>();
Sources = new List<ShaderIrBlock>();
Nodes = new List<ShaderIrNode>();
}
public void AddNode(ShaderIrNode Node)
@ -22,28 +28,6 @@ namespace Ryujinx.Graphics.Gal.Shader
Nodes.Add(Node);
}
public ShaderIrLabel GetLabel(long Position)
{
if (LabelsToInsert.TryGetValue(Position, out ShaderIrLabel Label))
{
return Label;
}
Label = new ShaderIrLabel();
LabelsToInsert.Add(Position, Label);
return Label;
}
public void MarkLabel(long Position)
{
if (LabelsToInsert.TryGetValue(Position, out ShaderIrLabel Label))
{
Nodes.Add(Label);
}
}
public ShaderIrNode[] GetNodes()
{
return Nodes.ToArray();

View file

@ -65,6 +65,8 @@ namespace Ryujinx.Graphics.Gal.Shader
Cne,
Lsl,
Lsr,
Max,
Min,
Mul,
Neg,
Not,

View file

@ -1,4 +0,0 @@
namespace Ryujinx.Graphics.Gal.Shader
{
class ShaderIrLabel : ShaderIrNode { }
}

View file

@ -63,6 +63,9 @@ namespace Ryujinx.Graphics.Gal.Shader
Set("0100110011100x", ShaderDecode.I2i_C);
Set("0011100x11100x", ShaderDecode.I2i_I);
Set("0101110011100x", ShaderDecode.I2i_R);
Set("0100110000100x", ShaderDecode.Imnmx_C);
Set("0011100x00100x", ShaderDecode.Imnmx_I);
Set("0101110000100x", ShaderDecode.Imnmx_R);
Set("11100000xxxxxx", ShaderDecode.Ipa);
Set("0100110000011x", ShaderDecode.Iscadd_C);
Set("0011100x00011x", ShaderDecode.Iscadd_I);
@ -89,6 +92,10 @@ namespace Ryujinx.Graphics.Gal.Shader
Set("1101111101001x", ShaderDecode.Texq);
Set("1101100xxxxxxx", ShaderDecode.Texs);
Set("1101101xxxxxxx", ShaderDecode.Tlds);
Set("0100111xxxxxxx", ShaderDecode.Xmad_CR);
Set("0011011x00xxxx", ShaderDecode.Xmad_I);
Set("010100010xxxxx", ShaderDecode.Xmad_RC);
Set("0101101100xxxx", ShaderDecode.Xmad_RR);
#endregion
}