aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics/Gal/Shader
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-04-08 16:17:35 -0300
committergdkchan <gab.dark.100@gmail.com>2018-04-08 16:41:38 -0300
commitb9aa3966c00b4bb3ff0292dc28ed53ad26cf284b (patch)
treecd2ab3d65c61ac6c6ceb312116e5d138868a3e18 /Ryujinx.Graphics/Gal/Shader
parent7acd0e01226d64d05b2675f6ae07507039a31835 (diff)
Merge shader branch, adding support for GLSL decompilation, a macro
interpreter, and a rewrite of the GPU code.
Diffstat (limited to 'Ryujinx.Graphics/Gal/Shader')
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslDecl.cs212
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs644
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslProgram.cs22
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs4
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs315
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs17
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs211
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs59
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs128
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs41
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs14
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs39
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs14
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs59
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs4
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs22
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs14
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs14
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs16
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs12
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs12
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs17
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs97
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderOper.cs11
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs266
25 files changed, 2264 insertions, 0 deletions
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
new file mode 100644
index 00000000..898b90b5
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
@@ -0,0 +1,212 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class GlslDecl
+ {
+ public const int VertexIdAttr = 0x2fc;
+ public const int GlPositionWAttr = 0x7c;
+
+ private const int AttrStartIndex = 8;
+ private const int TexStartIndex = 8;
+
+ private const string InAttrName = "in_attr";
+ private const string OutAttrName = "out_attr";
+ private const string UniformName = "c";
+
+ private const string GprName = "gpr";
+ private const string PredName = "pred";
+ private const string TextureName = "tex";
+
+ public const string FragmentOutputName = "FragColor";
+
+ private string[] StagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" };
+
+ private string StagePrefix;
+
+ private Dictionary<int, ShaderDeclInfo> m_Textures;
+
+ private Dictionary<(int, int), ShaderDeclInfo> m_Uniforms;
+
+ private Dictionary<int, ShaderDeclInfo> m_InAttributes;
+ private Dictionary<int, ShaderDeclInfo> m_OutAttributes;
+
+ private Dictionary<int, ShaderDeclInfo> m_Gprs;
+ private Dictionary<int, ShaderDeclInfo> m_Preds;
+
+ public IReadOnlyDictionary<int, ShaderDeclInfo> Textures => m_Textures;
+
+ public IReadOnlyDictionary<(int, int), ShaderDeclInfo> Uniforms => m_Uniforms;
+
+ public IReadOnlyDictionary<int, ShaderDeclInfo> InAttributes => m_InAttributes;
+ public IReadOnlyDictionary<int, ShaderDeclInfo> OutAttributes => m_OutAttributes;
+
+ public IReadOnlyDictionary<int, ShaderDeclInfo> Gprs => m_Gprs;
+ public IReadOnlyDictionary<int, ShaderDeclInfo> Preds => m_Preds;
+
+ public GalShaderType ShaderType { get; private set; }
+
+ public GlslDecl(ShaderIrNode[] Nodes, GalShaderType ShaderType)
+ {
+ this.ShaderType = ShaderType;
+
+ StagePrefix = StagePrefixes[(int)ShaderType] + "_";
+
+ m_Uniforms = new Dictionary<(int, int), ShaderDeclInfo>();
+
+ m_Textures = new Dictionary<int, ShaderDeclInfo>();
+
+ m_InAttributes = new Dictionary<int, ShaderDeclInfo>();
+ m_OutAttributes = new Dictionary<int, ShaderDeclInfo>();
+
+ m_Gprs = new Dictionary<int, ShaderDeclInfo>();
+ m_Preds = new Dictionary<int, ShaderDeclInfo>();
+
+ //FIXME: Only valid for vertex shaders.
+ if (ShaderType == GalShaderType.Fragment)
+ {
+ m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, 0, 4));
+ }
+ else
+ {
+ m_OutAttributes.Add(7, new ShaderDeclInfo("gl_Position", -1, 0, 4));
+ }
+
+ foreach (ShaderIrNode Node in Nodes)
+ {
+ Traverse(null, Node);
+ }
+ }
+
+ private void Traverse(ShaderIrNode Parent, ShaderIrNode Node)
+ {
+ switch (Node)
+ {
+ case ShaderIrAsg Asg:
+ {
+ Traverse(Asg, Asg.Dst);
+ Traverse(Asg, Asg.Src);
+
+ break;
+ }
+
+ case ShaderIrCond Cond:
+ {
+ Traverse(Cond, Cond.Pred);
+ Traverse(Cond, Cond.Child);
+
+ break;
+ }
+
+ case ShaderIrOp Op:
+ {
+ Traverse(Op, Op.OperandA);
+ Traverse(Op, Op.OperandB);
+ Traverse(Op, Op.OperandC);
+
+ if (Op.Inst == ShaderIrInst.Texr ||
+ Op.Inst == ShaderIrInst.Texg ||
+ Op.Inst == ShaderIrInst.Texb ||
+ Op.Inst == ShaderIrInst.Texa)
+ {
+ int Handle = ((ShaderIrOperImm)Op.OperandC).Value;
+
+ int Index = Handle - TexStartIndex;
+
+ string Name = StagePrefix + TextureName + Index;
+
+ m_Textures.TryAdd(Handle, new ShaderDeclInfo(Name, Handle));
+ }
+ break;
+ }
+
+ case ShaderIrOperCbuf Cbuf:
+ {
+ string Name = StagePrefix + UniformName + Cbuf.Index + "_" + Cbuf.Offs;
+
+ ShaderDeclInfo DeclInfo = new ShaderDeclInfo(Name, Cbuf.Offs, Cbuf.Index);
+
+ m_Uniforms.TryAdd((Cbuf.Index, Cbuf.Offs), DeclInfo);
+
+ break;
+ }
+
+ case ShaderIrOperAbuf Abuf:
+ {
+ //This is a built-in input variable.
+ if (Abuf.Offs == VertexIdAttr)
+ {
+ break;
+ }
+
+ int Index = Abuf.Offs >> 4;
+ int Elem = (Abuf.Offs >> 2) & 3;
+
+ int GlslIndex = Index - AttrStartIndex;
+
+ ShaderDeclInfo DeclInfo;
+
+ if (Parent is ShaderIrAsg Asg && Asg.Dst == Node)
+ {
+ if (!m_OutAttributes.TryGetValue(Index, out DeclInfo))
+ {
+ DeclInfo = new ShaderDeclInfo(OutAttrName + GlslIndex, GlslIndex);
+
+ m_OutAttributes.Add(Index, DeclInfo);
+ }
+ }
+ else
+ {
+ if (!m_InAttributes.TryGetValue(Index, out DeclInfo))
+ {
+ DeclInfo = new ShaderDeclInfo(InAttrName + GlslIndex, GlslIndex);
+
+ m_InAttributes.Add(Index, DeclInfo);
+ }
+ }
+
+ DeclInfo.Enlarge(Elem + 1);
+
+ break;
+ }
+
+ case ShaderIrOperGpr Gpr:
+ {
+ if (!Gpr.IsConst && !HasName(m_Gprs, Gpr.Index))
+ {
+ string Name = GprName + Gpr.Index;
+
+ m_Gprs.TryAdd(Gpr.Index, new ShaderDeclInfo(Name, Gpr.Index));
+ }
+ break;
+ }
+
+ case ShaderIrOperPred Pred:
+ {
+ if (!Pred.IsConst && !HasName(m_Preds, Pred.Index))
+ {
+ string Name = PredName + Pred.Index;
+
+ m_Preds.TryAdd(Pred.Index, new ShaderDeclInfo(Name, Pred.Index));
+ }
+ break;
+ }
+ }
+ }
+
+ private bool HasName(Dictionary<int, ShaderDeclInfo> Decls, int Index)
+ {
+ int VecIndex = Index >> 2;
+
+ if (Decls.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo))
+ {
+ if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size)
+ {
+ return true;
+ }
+ }
+
+ return Decls.ContainsKey(Index);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
new file mode 100644
index 00000000..eda70cef
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
@@ -0,0 +1,644 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class GlslDecompiler
+ {
+ private delegate string GetInstExpr(ShaderIrOp Op);
+
+ private Dictionary<ShaderIrInst, GetInstExpr> InstsExpr;
+
+ private enum OperType
+ {
+ Bool,
+ F32,
+ I32
+ }
+
+ private const string IdentationStr = " ";
+
+ private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" };
+
+ private GlslDecl Decl;
+
+ private StringBuilder SB;
+
+ public GlslDecompiler()
+ {
+ InstsExpr = new Dictionary<ShaderIrInst, GetInstExpr>()
+ {
+ { ShaderIrInst.And, GetAndExpr },
+ { ShaderIrInst.Asr, GetAsrExpr },
+ { ShaderIrInst.Band, GetBandExpr },
+ { ShaderIrInst.Bnot, GetBnotExpr },
+ { ShaderIrInst.Clt, GetCltExpr },
+ { ShaderIrInst.Ceq, GetCeqExpr },
+ { ShaderIrInst.Cle, GetCleExpr },
+ { ShaderIrInst.Cgt, GetCgtExpr },
+ { ShaderIrInst.Cne, GetCneExpr },
+ { ShaderIrInst.Cge, GetCgeExpr },
+ { ShaderIrInst.Exit, GetExitExpr },
+ { ShaderIrInst.Fabs, GetFabsExpr },
+ { ShaderIrInst.Fadd, GetFaddExpr },
+ { ShaderIrInst.Fcos, GetFcosExpr },
+ { ShaderIrInst.Fex2, GetFex2Expr },
+ { ShaderIrInst.Ffma, GetFfmaExpr },
+ { ShaderIrInst.Flg2, GetFlg2Expr },
+ { ShaderIrInst.Fmul, GetFmulExpr },
+ { ShaderIrInst.Fneg, GetFnegExpr },
+ { ShaderIrInst.Frcp, GetFrcpExpr },
+ { ShaderIrInst.Frsq, GetFrsqExpr },
+ { ShaderIrInst.Fsin, GetFsinExpr },
+ { ShaderIrInst.Ipa, GetIpaExpr },
+ { ShaderIrInst.Kil, GetKilExpr },
+ { ShaderIrInst.Lsr, GetLsrExpr },
+ { ShaderIrInst.Not, GetNotExpr },
+ { ShaderIrInst.Or, GetOrExpr },
+ { ShaderIrInst.Stof, GetStofExpr },
+ { ShaderIrInst.Utof, GetUtofExpr },
+ { ShaderIrInst.Texr, GetTexrExpr },
+ { ShaderIrInst.Texg, GetTexgExpr },
+ { ShaderIrInst.Texb, GetTexbExpr },
+ { ShaderIrInst.Texa, GetTexaExpr },
+ { ShaderIrInst.Xor, GetXorExpr },
+ };
+ }
+
+ public GlslProgram Decompile(int[] Code, GalShaderType ShaderType)
+ {
+ ShaderIrBlock Block = ShaderDecoder.DecodeBasicBlock(Code, 0, ShaderType);
+
+ ShaderIrNode[] Nodes = Block.GetNodes();
+
+ Decl = new GlslDecl(Nodes, ShaderType);
+
+ SB = new StringBuilder();
+
+ SB.AppendLine("#version 330 core");
+
+ PrintDeclTextures();
+ PrintDeclUniforms();
+ PrintDeclInAttributes();
+ PrintDeclOutAttributes();
+ PrintDeclGprs();
+ PrintDeclPreds();
+
+ PrintBlockScope("void main()", 1, Nodes);
+
+ string GlslCode = SB.ToString();
+
+ return new GlslProgram(
+ GlslCode,
+ Decl.Textures.Values,
+ Decl.Uniforms.Values);
+ }
+
+ private void PrintDeclTextures()
+ {
+ PrintDecls(Decl.Textures, "uniform sampler2D");
+ }
+
+ private void PrintDeclUniforms()
+ {
+ foreach (ShaderDeclInfo DeclInfo in Decl.Uniforms.Values.OrderBy(DeclKeySelector))
+ {
+ SB.AppendLine($"uniform {GetDecl(DeclInfo)};");
+ }
+
+ if (Decl.Uniforms.Count > 0)
+ {
+ SB.AppendLine();
+ }
+ }
+
+ private void PrintDeclInAttributes()
+ {
+ PrintDeclAttributes(Decl.InAttributes.Values, "in");
+ }
+
+ private void PrintDeclOutAttributes()
+ {
+ PrintDeclAttributes(Decl.OutAttributes.Values, "out");
+ }
+
+ private void PrintDeclAttributes(IEnumerable<ShaderDeclInfo> Decls, string InOut)
+ {
+ int Count = 0;
+
+ foreach (ShaderDeclInfo DeclInfo in Decls.OrderBy(DeclKeySelector))
+ {
+ if (DeclInfo.Index >= 0)
+ {
+ SB.AppendLine($"layout (location = {DeclInfo.Index}) {InOut} {GetDecl(DeclInfo)};");
+
+ Count++;
+ }
+ }
+
+ if (Count > 0)
+ {
+ SB.AppendLine();
+ }
+ }
+
+ private void PrintDeclGprs()
+ {
+ PrintDecls(Decl.Gprs);
+ }
+
+ private void PrintDeclPreds()
+ {
+ PrintDecls(Decl.Preds, "bool");
+ }
+
+ private void PrintDecls(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, string CustomType = null)
+ {
+ foreach (ShaderDeclInfo DeclInfo in Dict.Values.OrderBy(DeclKeySelector))
+ {
+ string Name;
+
+ if (CustomType != null)
+ {
+ Name = CustomType + " " + DeclInfo.Name + ";";
+ }
+ else if (DeclInfo.Name == GlslDecl.FragmentOutputName)
+ {
+ Name = "layout (location = 0) out " + GetDecl(DeclInfo) + ";";
+ }
+ else
+ {
+ Name = GetDecl(DeclInfo) + ";";
+ }
+
+ SB.AppendLine(Name);
+ }
+
+ if (Dict.Count > 0)
+ {
+ SB.AppendLine();
+ }
+ }
+
+ private int DeclKeySelector(ShaderDeclInfo DeclInfo)
+ {
+ return DeclInfo.Cbuf << 24 | DeclInfo.Index;
+ }
+
+ private string GetDecl(ShaderDeclInfo DeclInfo)
+ {
+ return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name;
+ }
+
+ private void PrintBlockScope(string ScopeName, int IdentationLevel, params ShaderIrNode[] Nodes)
+ {
+ string Identation = string.Empty;
+
+ for (int Index = 0; Index < IdentationLevel - 1; Index++)
+ {
+ Identation += IdentationStr;
+ }
+
+ if (ScopeName != string.Empty)
+ {
+ ScopeName += " ";
+ }
+
+ SB.AppendLine(Identation + ScopeName + "{");
+
+ string LastLine = Identation + "}";
+
+ if (IdentationLevel > 0)
+ {
+ Identation += IdentationStr;
+ }
+
+ for (int Index = 0; Index < Nodes.Length; Index++)
+ {
+ ShaderIrNode Node = Nodes[Index];
+
+ if (Node is ShaderIrCond Cond)
+ {
+ string SubScopeName = "if (" + GetSrcExpr(Cond.Pred, true) + ")";
+
+ PrintBlockScope(SubScopeName, IdentationLevel + 1, Cond.Child);
+ }
+ else if (Node is ShaderIrAsg Asg && IsValidOutOper(Asg.Dst))
+ {
+ string Expr = GetSrcExpr(Asg.Src, true);
+
+ Expr = GetExprWithCast(Asg.Dst, Asg.Src, Expr);
+
+ SB.AppendLine(Identation + GetDstOperName(Asg.Dst) + " = " + Expr + ";");
+ }
+ else if (Node is ShaderIrOp Op)
+ {
+ SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";");
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ SB.AppendLine(LastLine);
+ }
+
+ private bool IsValidOutOper(ShaderIrNode Node)
+ {
+ if (Node is ShaderIrOperGpr Gpr && Gpr.IsConst)
+ {
+ return false;
+ }
+ else if (Node is ShaderIrOperPred Pred && Pred.IsConst)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private string GetDstOperName(ShaderIrNode Node)
+ {
+ if (Node is ShaderIrOperAbuf Abuf)
+ {
+ return GetOutAbufName(Abuf);
+ }
+ else if (Node is ShaderIrOperGpr Gpr)
+ {
+ return GetName(Gpr);
+ }
+ else if (Node is ShaderIrOperPred Pred)
+ {
+ return GetName(Pred);
+ }
+
+ throw new ArgumentException(nameof(Node));
+ }
+
+ private string GetSrcExpr(ShaderIrNode Node, bool Entry = false)
+ {
+ switch (Node)
+ {
+ case ShaderIrOperAbuf Abuf: return GetName (Abuf);
+ case ShaderIrOperCbuf Cbuf: return GetName (Cbuf);
+ case ShaderIrOperGpr Gpr: return GetName (Gpr);
+ case ShaderIrOperImm Imm: return GetValue(Imm);
+ case ShaderIrOperImmf Immf: return GetValue(Immf);
+ case ShaderIrOperPred Pred: return GetName (Pred);
+
+ case ShaderIrOp Op:
+ string Expr;
+
+ if (InstsExpr.TryGetValue(Op.Inst, out GetInstExpr GetExpr))
+ {
+ Expr = GetExpr(Op);
+ }
+ else
+ {
+ throw new NotImplementedException(Op.Inst.ToString());
+ }
+
+ if (!Entry && NeedsParentheses(Op))
+ {
+ Expr = "(" + Expr + ")";
+ }
+
+ return Expr;
+
+ default: throw new ArgumentException(nameof(Node));
+ }
+ }
+
+ private static bool NeedsParentheses(ShaderIrOp Op)
+ {
+ switch (Op.Inst)
+ {
+ case ShaderIrInst.Frcp:
+ return true;
+
+ case ShaderIrInst.Ipa:
+ case ShaderIrInst.Texr:
+ case ShaderIrInst.Texg:
+ case ShaderIrInst.Texb:
+ case ShaderIrInst.Texa:
+ return false;
+ }
+
+ return Op.OperandB != null ||
+ Op.OperandC != null;
+ }
+
+ private string GetName(ShaderIrOperCbuf Cbuf)
+ {
+ if (!Decl.Uniforms.TryGetValue((Cbuf.Index, Cbuf.Offs), out ShaderDeclInfo DeclInfo))
+ {
+ throw new InvalidOperationException();
+ }
+
+ return DeclInfo.Name;
+ }
+
+ private string GetOutAbufName(ShaderIrOperAbuf Abuf)
+ {
+ return GetName(Decl.OutAttributes, Abuf);
+ }
+
+ private string GetName(ShaderIrOperAbuf Abuf)
+ {
+ if (Abuf.Offs == GlslDecl.GlPositionWAttr && Decl.ShaderType == GalShaderType.Fragment)
+ {
+ return "(1f / gl_FragCoord.w)";
+ }
+
+ if (Abuf.Offs == GlslDecl.VertexIdAttr)
+ {
+ return "gl_VertexID";
+ }
+
+ return GetName(Decl.InAttributes, Abuf);
+ }
+
+ private string GetName(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, ShaderIrOperAbuf Abuf)
+ {
+ int Index = Abuf.Offs >> 4;
+ int Elem = (Abuf.Offs >> 2) & 3;
+
+ if (!Dict.TryGetValue(Index, out ShaderDeclInfo DeclInfo))
+ {
+ throw new InvalidOperationException();
+ }
+
+ return DeclInfo.Size > 1 ? DeclInfo.Name + "." + GetAttrSwizzle(Elem) : DeclInfo.Name;
+ }
+
+ private string GetName(ShaderIrOperGpr Gpr)
+ {
+ return Gpr.IsConst ? "0" : GetNameWithSwizzle(Decl.Gprs, Gpr.Index);
+ }
+
+ private string GetValue(ShaderIrOperImm Imm)
+ {
+ //Only use hex is the value is too big and would likely be hard to read as int.
+ if (Imm.Value > 0xfff ||
+ Imm.Value < -0xfff)
+ {
+ return "0x" + Imm.Value.ToString("x8", CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ return Imm.Value.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ private string GetValue(ShaderIrOperImmf Immf)
+ {
+ return Immf.Value.ToString(CultureInfo.InvariantCulture) + "f";
+ }
+
+ private string GetName(ShaderIrOperPred Pred)
+ {
+ return Pred.IsConst ? "true" : GetNameWithSwizzle(Decl.Preds, Pred.Index);
+ }
+
+ private string GetNameWithSwizzle(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, int Index)
+ {
+ int VecIndex = Index >> 2;
+
+ if (Dict.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo))
+ {
+ if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size)
+ {
+ return DeclInfo.Name + "." + GetAttrSwizzle(Index & 3);
+ }
+ }
+
+ if (!Dict.TryGetValue(Index, out DeclInfo))
+ {
+ throw new InvalidOperationException();
+ }
+
+ return DeclInfo.Name;
+ }
+
+ private string GetAttrSwizzle(int Elem)
+ {
+ return "xyzw".Substring(Elem, 1);
+ }
+
+ private string GetAndExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&");
+
+ private string GetAsrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">>");
+
+ private string GetBandExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&&");
+
+ private string GetBnotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "!");
+
+ 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 GetCgeExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">=");
+
+ private string GetExitExpr(ShaderIrOp Op) => "return";
+
+ private string GetFabsExpr(ShaderIrOp Op) => GetUnaryCall(Op, "abs");
+
+ private string GetFaddExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "+");
+
+ private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos");
+
+ private string GetFex2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "exp2");
+
+ private string GetFfmaExpr(ShaderIrOp Op) => GetTernaryExpr(Op, "*", "+");
+
+ private string GetFlg2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "log2");
+
+ private string GetFmulExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "*");
+
+ private string GetFnegExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "-");
+
+ private string GetFrcpExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "1f / ");
+
+ private string GetFrsqExpr(ShaderIrOp Op) => GetUnaryCall(Op, "inversesqrt");
+
+ private string GetFsinExpr(ShaderIrOp Op) => GetUnaryCall(Op, "sin");
+
+ private string GetIpaExpr(ShaderIrOp Op) => GetSrcExpr(Op.OperandA);
+
+ private string GetKilExpr(ShaderIrOp Op) => "discard";
+
+ private string GetLsrExpr(ShaderIrOp Op)
+ {
+ return "int(uint(" + GetOperExpr(Op, Op.OperandA) + ") >> " +
+ GetOperExpr(Op, Op.OperandB) + ")";
+ }
+
+ private string GetNotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "~");
+
+ private string GetOrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "|");
+
+ private string GetStofExpr(ShaderIrOp Op)
+ {
+ return "float(" + GetOperExpr(Op, Op.OperandA) + ")";
+ }
+
+ private string GetUtofExpr(ShaderIrOp Op)
+ {
+ return "float(uint(" + GetOperExpr(Op, Op.OperandA) + "))";
+ }
+
+ private string GetXorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^");
+
+ private string GetUnaryCall(ShaderIrOp Op, string FuncName)
+ {
+ return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ")";
+ }
+
+ private string GetUnaryExpr(ShaderIrOp Op, string Opr)
+ {
+ return Opr + GetOperExpr(Op, Op.OperandA);
+ }
+
+ private string GetBinaryExpr(ShaderIrOp Op, string Opr)
+ {
+ return GetOperExpr(Op, Op.OperandA) + " " + Opr + " " +
+ GetOperExpr(Op, Op.OperandB);
+ }
+
+ private string GetTernaryExpr(ShaderIrOp Op, string Opr1, string Opr2)
+ {
+ return GetOperExpr(Op, Op.OperandA) + " " + Opr1 + " " +
+ GetOperExpr(Op, Op.OperandB) + " " + Opr2 + " " +
+ GetOperExpr(Op, Op.OperandC);
+ }
+
+ private string GetTexrExpr(ShaderIrOp Op) => GetTexExpr(Op, 'r');
+ private string GetTexgExpr(ShaderIrOp Op) => GetTexExpr(Op, 'g');
+ private string GetTexbExpr(ShaderIrOp Op) => GetTexExpr(Op, 'b');
+ private string GetTexaExpr(ShaderIrOp Op) => GetTexExpr(Op, 'a');
+
+ private string GetTexExpr(ShaderIrOp Op, char Ch)
+ {
+ return $"texture({GetTexSamplerName(Op)}, {GetTexSamplerCoords(Op)}).{Ch}";
+ }
+
+ private string GetTexSamplerName(ShaderIrOp Op)
+ {
+ ShaderIrOperImm Node = (ShaderIrOperImm)Op.OperandC;
+
+ int Handle = ((ShaderIrOperImm)Op.OperandC).Value;
+
+ if (!Decl.Textures.TryGetValue(Handle, out ShaderDeclInfo DeclInfo))
+ {
+ throw new InvalidOperationException();
+ }
+
+ return DeclInfo.Name;
+ }
+
+ private string GetTexSamplerCoords(ShaderIrOp Op)
+ {
+ return "vec2(" + GetOperExpr(Op, Op.OperandA) + ", " +
+ GetOperExpr(Op, Op.OperandB) + ")";
+ }
+
+ private string GetOperExpr(ShaderIrOp Op, ShaderIrNode Oper)
+ {
+ return GetExprWithCast(Op, Oper, GetSrcExpr(Oper));
+ }
+
+ private static string GetExprWithCast(ShaderIrNode Dst, ShaderIrNode Src, string Expr)
+ {
+ //Note: The "DstType" (of the cast) is the type that the operation
+ //uses on the source operands, while the "SrcType" is the destination
+ //type of the operand result (if it is a operation) or just the type
+ //of the variable for registers/uniforms/attributes.
+ OperType DstType = GetSrcNodeType(Dst);
+ OperType SrcType = GetDstNodeType(Src);
+
+ if (DstType != SrcType)
+ {
+ //Check for invalid casts
+ //(like bool to int/float and others).
+ if (SrcType != OperType.F32 &&
+ SrcType != OperType.I32)
+ {
+ throw new InvalidOperationException();
+ }
+
+ //For integer immediates being used as float,
+ //it's better (for readability) to just return the float value.
+ if (Src is ShaderIrOperImm Imm && DstType == OperType.F32)
+ {
+ float Value = BitConverter.Int32BitsToSingle(Imm.Value);
+
+ return Value.ToString(CultureInfo.InvariantCulture) + "f";
+ }
+
+ switch (DstType)
+ {
+ case OperType.F32: Expr = "intBitsToFloat(" + Expr + ")"; break;
+ case OperType.I32: Expr = "floatBitsToInt(" + Expr + ")"; break;
+ }
+ }
+
+ return Expr;
+ }
+
+ private static OperType GetDstNodeType(ShaderIrNode Node)
+ {
+ if (Node is ShaderIrOp Op)
+ {
+ switch (Op.Inst)
+ {
+ case ShaderIrInst.Stof: return OperType.F32;
+ case ShaderIrInst.Utof: return OperType.F32;
+ }
+ }
+
+ return GetSrcNodeType(Node);
+ }
+
+ private static OperType GetSrcNodeType(ShaderIrNode Node)
+ {
+ switch (Node)
+ {
+ case ShaderIrOperAbuf Abuf:
+ return Abuf.Offs == GlslDecl.VertexIdAttr
+ ? OperType.I32
+ : OperType.F32;
+
+ case ShaderIrOperCbuf Cbuf: return OperType.F32;
+ case ShaderIrOperGpr Gpr: return OperType.F32;
+ case ShaderIrOperImm Imm: return OperType.I32;
+ case ShaderIrOperImmf Immf: return OperType.F32;
+ case ShaderIrOperPred Pred: return OperType.Bool;
+
+ case ShaderIrOp Op:
+ if (Op.Inst > ShaderIrInst.B_Start &&
+ Op.Inst < ShaderIrInst.B_End)
+ {
+ return OperType.Bool;
+ }
+ else if (Op.Inst > ShaderIrInst.F_Start &&
+ Op.Inst < ShaderIrInst.F_End)
+ {
+ return OperType.F32;
+ }
+ else if (Op.Inst > ShaderIrInst.I_Start &&
+ Op.Inst < ShaderIrInst.I_End)
+ {
+ return OperType.I32;
+ }
+ break;
+ }
+
+ throw new ArgumentException(nameof(Node));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs
new file mode 100644
index 00000000..729b6f1d
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ struct GlslProgram
+ {
+ public string Code { get; private set; }
+
+ public IEnumerable<ShaderDeclInfo> Textures { get; private set; }
+ public IEnumerable<ShaderDeclInfo> Uniforms { get; private set; }
+
+ public GlslProgram(
+ string Code,
+ IEnumerable<ShaderDeclInfo> Textures,
+ IEnumerable<ShaderDeclInfo> Uniforms)
+ {
+ this.Code = Code;
+ this.Textures = Textures;
+ this.Uniforms = Uniforms;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs
new file mode 100644
index 00000000..ef0fd78b
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode);
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
new file mode 100644
index 00000000..5c2f493e
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
@@ -0,0 +1,315 @@
+using System;
+
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static partial class ShaderDecode
+ {
+ public static void Fadd_C(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fadd);
+ }
+
+ public static void Fadd_I(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fadd);
+ }
+
+ public static void Fadd_R(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fadd);
+ }
+
+ public static void Ffma_CR(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluFfma(Block, OpCode, ShaderOper.CR);
+ }
+
+ public static void Ffma_I(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluFfma(Block, OpCode, ShaderOper.Immf);
+ }
+
+ public static void Ffma_RC(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluFfma(Block, OpCode, ShaderOper.RC);
+ }
+
+ public static void Ffma_RR(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluFfma(Block, OpCode, ShaderOper.RR);
+ }
+
+ public static void Fmul_C(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fmul);
+ }
+
+ public static void Fmul_I(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fmul);
+ }
+
+ public static void Fmul_R(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fmul);
+ }
+
+ public static void Fsetp_C(ShaderIrBlock Block, long OpCode)
+ {
+ EmitFsetp(Block, OpCode, ShaderOper.CR);
+ }
+
+ public static void Fsetp_I(ShaderIrBlock Block, long OpCode)
+ {
+ EmitFsetp(Block, OpCode, ShaderOper.Immf);
+ }
+
+ public static void Fsetp_R(ShaderIrBlock Block, long OpCode)
+ {
+ EmitFsetp(Block, OpCode, ShaderOper.RR);
+ }
+
+ public static void Ipa(ShaderIrBlock Block, long OpCode)
+ {
+ ShaderIrNode OperA = GetOperAbuf28(OpCode);
+ ShaderIrNode OperB = GetOperGpr20 (OpCode);
+
+ ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ipa, OperA, OperB);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+ }
+
+ public static void Lop32i(ShaderIrBlock Block, long OpCode)
+ {
+ int SubOp = (int)(OpCode >> 53) & 3;
+
+ bool Ia = ((OpCode >> 55) & 1) != 0;
+ bool Ib = ((OpCode >> 56) & 1) != 0;
+
+ ShaderIrInst Inst = 0;
+
+ switch (SubOp)
+ {
+ case 0: Inst = ShaderIrInst.And; break;
+ case 1: Inst = ShaderIrInst.Or; break;
+ case 2: Inst = ShaderIrInst.Xor; break;
+ }
+
+ ShaderIrNode OperA = GetAluNot(GetOperGpr8(OpCode), Ia);
+
+ //SubOp == 3 is pass, used by the not instruction
+ //which just moves the inverted register value.
+ if (SubOp < 3)
+ {
+ ShaderIrNode OperB = GetAluNot(GetOperImm32_20(OpCode), Ib);
+
+ ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+ }
+ else
+ {
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode));
+ }
+ }
+
+ public static void Mufu(ShaderIrBlock Block, long OpCode)
+ {
+ int SubOp = (int)(OpCode >> 20) & 7;
+
+ bool Aa = ((OpCode >> 46) & 1) != 0;
+ bool Na = ((OpCode >> 48) & 1) != 0;
+
+ ShaderIrInst Inst = 0;
+
+ switch (SubOp)
+ {
+ case 0: Inst = ShaderIrInst.Fcos; break;
+ case 1: Inst = ShaderIrInst.Fsin; break;
+ case 2: Inst = ShaderIrInst.Fex2; break;
+ case 3: Inst = ShaderIrInst.Flg2; break;
+ case 4: Inst = ShaderIrInst.Frcp; break;
+ case 5: Inst = ShaderIrInst.Frsq; break;
+
+ default: throw new NotImplementedException(SubOp.ToString());
+ }
+
+ ShaderIrNode OperA = GetOperGpr8(OpCode);
+
+ ShaderIrOp Op = new ShaderIrOp(Inst, GetAluAbsNeg(OperA, Aa, Na));
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+ }
+
+ public static void Shr_C(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinary(Block, OpCode, ShaderOper.CR, GetShrInst(OpCode));
+ }
+
+ public static void Shr_I(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinary(Block, OpCode, ShaderOper.Imm, GetShrInst(OpCode));
+ }
+
+ public static void Shr_R(ShaderIrBlock Block, long OpCode)
+ {
+ EmitAluBinary(Block, OpCode, ShaderOper.RR, GetShrInst(OpCode));
+ }
+
+ private static ShaderIrInst GetShrInst(long OpCode)
+ {
+ bool Signed = ((OpCode >> 48) & 1) != 0;
+
+ return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
+ }
+
+ private static void EmitAluBinary(
+ ShaderIrBlock Block,
+ long OpCode,
+ ShaderOper Oper,
+ ShaderIrInst Inst)
+ {
+ ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
+
+ switch (Oper)
+ {
+ case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break;
+ case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break;
+ case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break;
+
+ default: throw new ArgumentException(nameof(Oper));
+ }
+
+ ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+ }
+
+ private static void EmitAluBinaryF(
+ ShaderIrBlock Block,
+ long OpCode,
+ ShaderOper Oper,
+ ShaderIrInst Inst)
+ {
+ bool Nb = ((OpCode >> 45) & 1) != 0;
+ bool Aa = ((OpCode >> 46) & 1) != 0;
+ bool Na = ((OpCode >> 48) & 1) != 0;
+ bool Ab = ((OpCode >> 49) & 1) != 0;
+ bool Ad = ((OpCode >> 50) & 1) != 0;
+
+ ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
+
+ if (Inst == ShaderIrInst.Fadd)
+ {
+ OperA = GetAluAbsNeg(OperA, Aa, Na);
+ }
+
+ switch (Oper)
+ {
+ case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break;
+ case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
+ case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break;
+
+ default: throw new ArgumentException(nameof(Oper));
+ }
+
+ OperB = GetAluAbsNeg(OperB, Ab, Nb);
+
+ ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB);
+
+ Op = GetAluAbs(Op, Ad);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+ }
+
+ private static void EmitAluFfma(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+ {
+ bool Nb = ((OpCode >> 48) & 1) != 0;
+ bool Nc = ((OpCode >> 49) & 1) != 0;
+
+ ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC;
+
+ switch (Oper)
+ {
+ case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break;
+ case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
+ case ShaderOper.RC: OperB = GetOperGpr39 (OpCode); break;
+ case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break;
+
+ default: throw new ArgumentException(nameof(Oper));
+ }
+
+ OperB = GetAluNeg(OperB, Nb);
+
+ if (Oper == ShaderOper.RC)
+ {
+ OperC = GetAluNeg(GetOperCbuf34(OpCode), Nc);
+ }
+ else
+ {
+ OperC = GetAluNeg(GetOperGpr39(OpCode), Nc);
+ }
+
+ ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ffma, OperA, OperB, OperC);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+ }
+
+ private static void EmitFsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+ {
+ bool Aa = ((OpCode >> 7) & 1) != 0;
+ bool Np = ((OpCode >> 42) & 1) != 0;
+ bool Na = ((OpCode >> 43) & 1) != 0;
+ bool Ab = ((OpCode >> 44) & 1) != 0;
+
+ ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
+
+ switch (Oper)
+ {
+ case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break;
+ case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
+ case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break;
+
+ default: throw new ArgumentException(nameof(Oper));
+ }
+
+ ShaderIrInst CmpInst = GetCmp(OpCode);
+
+ ShaderIrOp Op = new ShaderIrOp(CmpInst,
+ GetAluAbsNeg(OperA, Aa, Na),
+ GetAluAbs (OperB, Ab));
+
+ ShaderIrOperPred P0Node = GetOperPred3 (OpCode);
+ ShaderIrOperPred P1Node = GetOperPred0 (OpCode);
+ ShaderIrOperPred P2Node = GetOperPred39(OpCode);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode));
+
+ ShaderIrInst LopInst = GetBLop(OpCode);
+
+ if (LopInst == ShaderIrInst.Band && P1Node.IsConst && P2Node.IsConst)
+ {
+ return;
+ }
+
+ ShaderIrNode P2NNode = P2Node;
+
+ if (Np)
+ {
+ P2NNode = new ShaderIrOp(ShaderIrInst.Bnot, P2NNode);
+ }
+
+ Op = new ShaderIrOp(ShaderIrInst.Bnot, P0Node);
+
+ Op = new ShaderIrOp(LopInst, Op, P2NNode);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(P1Node, Op), OpCode));
+
+ Op = new ShaderIrOp(LopInst, P0Node, P2NNode);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
new file mode 100644
index 00000000..d3feb92e
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
@@ -0,0 +1,17 @@
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static partial class ShaderDecode
+ {
+ public static void Exit(ShaderIrBlock Block, long OpCode)
+ {
+ Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Exit), OpCode));
+ }
+
+ public static void Kil(ShaderIrBlock Block, long OpCode)
+ {
+ Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Kil), OpCode));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs
new file mode 100644
index 00000000..7989570d
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs
@@ -0,0 +1,211 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static class ShaderDecodeHelper
+ {
+ public static ShaderIrOperAbuf[] GetOperAbuf20(long OpCode)
+ {
+ int Abuf = (int)(OpCode >> 20) & 0x3ff;
+ int Reg = (int)(OpCode >> 39) & 0xff;
+ int Size = (int)(OpCode >> 47) & 3;
+
+ ShaderIrOperAbuf[] Opers = new ShaderIrOperAbuf[Size + 1];
+
+ for (int Index = 0; Index <= Size; Index++)
+ {
+ Opers[Index] = new ShaderIrOperAbuf(Abuf, Reg);
+ }
+
+ return Opers;
+ }
+
+ public static ShaderIrOperAbuf GetOperAbuf28(long OpCode)
+ {
+ int Abuf = (int)(OpCode >> 28) & 0x3ff;
+ int Reg = (int)(OpCode >> 39) & 0xff;
+
+ return new ShaderIrOperAbuf(Abuf, Reg);
+ }
+
+ public static ShaderIrOperCbuf GetOperCbuf34(long OpCode)
+ {
+ return new ShaderIrOperCbuf(
+ (int)(OpCode >> 34) & 0x1f,
+ (int)(OpCode >> 20) & 0x3fff);
+ }
+
+ public static ShaderIrOperGpr GetOperGpr8(long OpCode)
+ {
+ return new ShaderIrOperGpr((int)(OpCode >> 8) & 0xff);
+ }
+
+ public static ShaderIrOperGpr GetOperGpr20(long OpCode)
+ {
+ return new ShaderIrOperGpr((int)(OpCode >> 20) & 0xff);
+ }
+
+ public static ShaderIrOperGpr GetOperGpr39(long OpCode)
+ {
+ return new ShaderIrOperGpr((int)(OpCode >> 39) & 0xff);
+ }
+
+ public static ShaderIrOperGpr GetOperGpr0(long OpCode)
+ {
+ return new ShaderIrOperGpr((int)(OpCode >> 0) & 0xff);
+ }
+
+ public static ShaderIrOperGpr GetOperGpr28(long OpCode)
+ {
+ return new ShaderIrOperGpr((int)(OpCode >> 28) & 0xff);
+ }
+
+ public static ShaderIrNode GetOperImm19_20(long OpCode)
+ {
+ int Value = (int)(OpCode >> 20) & 0x7ffff;
+
+ bool Neg = ((OpCode >> 56) & 1) != 0;
+
+ if (Neg)
+ {
+ Value = -Value;
+ }
+
+ return new ShaderIrOperImm((int)Value);
+ }
+
+ public static ShaderIrNode GetOperImmf19_20(long OpCode)
+ {
+ uint Imm = (uint)(OpCode >> 20) & 0x7ffff;
+
+ bool Neg = ((OpCode >> 56) & 1) != 0;
+
+ Imm <<= 12;
+
+ if (Neg)
+ {
+ Imm |= 0x80000000;
+ }
+
+ float Value = BitConverter.Int32BitsToSingle((int)Imm);
+
+ return new ShaderIrOperImmf(Value);
+ }
+
+ public static ShaderIrOperImm GetOperImm13_36(long OpCode)
+ {
+ return new ShaderIrOperImm((int)(OpCode >> 36) & 0x1fff);
+ }
+
+ public static ShaderIrOperImm GetOperImm32_20(long OpCode)
+ {
+ return new ShaderIrOperImm((int)(OpCode >> 20));
+ }
+
+ public static ShaderIrOperPred GetOperPred3(long OpCode)
+ {
+ return new ShaderIrOperPred((int)(OpCode >> 3) & 7);
+ }
+
+ public static ShaderIrOperPred GetOperPred0(long OpCode)
+ {
+ return new ShaderIrOperPred((int)(OpCode >> 0) & 7);
+ }
+
+ public static ShaderIrNode GetOperPred39N(long OpCode)
+ {
+ ShaderIrNode Node = GetOperPred39(OpCode);
+
+ if (((OpCode >> 42) & 1) != 0)
+ {
+ Node = new ShaderIrOp(ShaderIrInst.Bnot, Node);
+ }
+
+ return Node;
+ }
+
+ public static ShaderIrOperPred GetOperPred39(long OpCode)
+ {
+ return new ShaderIrOperPred((int)(OpCode >> 39) & 7);
+ }
+
+ public static ShaderIrInst GetCmp(long OpCode)
+ {
+ switch ((int)(OpCode >> 48) & 0xf)
+ {
+ case 0x1: return ShaderIrInst.Clt;
+ case 0x2: return ShaderIrInst.Ceq;
+ case 0x3: return ShaderIrInst.Cle;
+ case 0x4: return ShaderIrInst.Cgt;
+ case 0x5: return ShaderIrInst.Cne;
+ case 0x6: return ShaderIrInst.Cge;
+ case 0x7: return ShaderIrInst.Cnum;
+ case 0x8: return ShaderIrInst.Cnan;
+ case 0x9: return ShaderIrInst.Cltu;
+ case 0xa: return ShaderIrInst.Cequ;
+ case 0xb: return ShaderIrInst.Cleu;
+ case 0xc: return ShaderIrInst.Cgtu;
+ case 0xd: return ShaderIrInst.Cneu;
+ case 0xe: return ShaderIrInst.Cgeu;
+ }
+
+ throw new ArgumentException(nameof(OpCode));
+ }
+
+ public static ShaderIrInst GetBLop(long OpCode)
+ {
+ switch ((int)(OpCode >> 45) & 3)
+ {
+ case 0: return ShaderIrInst.Band;
+ case 1: return ShaderIrInst.Bor;
+ case 2: return ShaderIrInst.Bxor;
+ }
+
+ throw new ArgumentException(nameof(OpCode));
+ }
+
+ public static ShaderIrNode GetPredNode(ShaderIrNode Node, long OpCode)
+ {
+ ShaderIrOperPred Pred = GetPredNode(OpCode);
+
+ if (Pred.Index != ShaderIrOperPred.UnusedIndex)
+ {
+ Node = new ShaderIrCond(Pred, Node);
+ }
+
+ return Node;
+ }
+
+ private static ShaderIrOperPred GetPredNode(long OpCode)
+ {
+ int Pred = (int)(OpCode >> 16) & 0xf;
+
+ if (Pred != 0xf)
+ {
+ Pred &= 7;
+ }
+
+ return new ShaderIrOperPred(Pred);
+ }
+
+ public static ShaderIrNode GetAluAbsNeg(ShaderIrNode Node, bool Abs, bool Neg)
+ {
+ return GetAluNeg(GetAluAbs(Node, Abs), Neg);
+ }
+
+ public static ShaderIrNode GetAluAbs(ShaderIrNode Node, bool Abs)
+ {
+ return Abs ? new ShaderIrOp(ShaderIrInst.Fabs, Node) : Node;
+ }
+
+ public static ShaderIrNode GetAluNeg(ShaderIrNode Node, bool Neg)
+ {
+ return Neg ? new ShaderIrOp(ShaderIrInst.Fneg, Node) : Node;
+ }
+
+ public static ShaderIrNode GetAluNot(ShaderIrNode Node, bool Not)
+ {
+ return Not ? new ShaderIrOp(ShaderIrInst.Not, Node) : Node;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
new file mode 100644
index 00000000..fd18ce07
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
@@ -0,0 +1,59 @@
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static partial class ShaderDecode
+ {
+ public static void Ld_A(ShaderIrBlock Block, long OpCode)
+ {
+ ShaderIrNode[] Opers = GetOperAbuf20(OpCode);
+
+ int Index = 0;
+
+ foreach (ShaderIrNode OperA in Opers)
+ {
+ ShaderIrOperGpr OperD = GetOperGpr0(OpCode);
+
+ OperD.Index += Index++;
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, OperA), OpCode));
+ }
+ }
+
+ public static void St_A(ShaderIrBlock Block, long OpCode)
+ {
+ ShaderIrNode[] Opers = GetOperAbuf20(OpCode);
+
+ int Index = 0;
+
+ foreach (ShaderIrNode OperA in Opers)
+ {
+ ShaderIrOperGpr OperD = GetOperGpr0(OpCode);
+
+ OperD.Index += Index++;
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, OperD), OpCode));
+ }
+ }
+
+ public static void Texs(ShaderIrBlock Block, long OpCode)
+ {
+ //TODO: Support other formats.
+ ShaderIrNode OperA = GetOperGpr8 (OpCode);
+ ShaderIrNode OperB = GetOperGpr20 (OpCode);
+ ShaderIrNode OperC = GetOperGpr28 (OpCode);
+ ShaderIrNode OperD = GetOperImm13_36(OpCode);
+
+ for (int Ch = 0; Ch < 4; Ch++)
+ {
+ ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Texr + Ch, OperA, OperB, OperD);
+
+ ShaderIrOperGpr Dst = GetOperGpr0(OpCode);
+
+ Dst.Index += Ch;
+
+ Block.AddNode(new ShaderIrAsg(Dst, Op));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
new file mode 100644
index 00000000..50c740bf
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
@@ -0,0 +1,128 @@
+using System;
+
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static partial class ShaderDecode
+ {
+ private enum IntType
+ {
+ U8 = 0,
+ U16 = 1,
+ U32 = 2,
+ U64 = 3,
+ S8 = 4,
+ S16 = 5,
+ S32 = 6,
+ S64 = 7
+ }
+
+ private enum FloatType
+ {
+ F16 = 1,
+ F32 = 2,
+ F64 = 3
+ }
+
+ public static void I2f_C(ShaderIrBlock Block, long OpCode)
+ {
+ EmitI2f(Block, OpCode, ShaderOper.CR);
+ }
+
+ public static void I2f_I(ShaderIrBlock Block, long OpCode)
+ {
+ EmitI2f(Block, OpCode, ShaderOper.Imm);
+ }
+
+ public static void I2f_R(ShaderIrBlock Block, long OpCode)
+ {
+ EmitI2f(Block, OpCode, ShaderOper.RR);
+ }
+
+ private static void EmitI2f(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+ {
+ IntType Type = GetIntType(OpCode);
+
+ if (Type == IntType.U64 ||
+ Type == IntType.S64)
+ {
+ //TODO: 64-bits support.
+ //Note: GLSL doesn't support 64-bits integers.
+ throw new NotImplementedException();
+ }
+
+ int Sel = (int)(OpCode >> 41) & 3;
+
+ bool Na = ((OpCode >> 45) & 1) != 0;
+ bool Aa = ((OpCode >> 49) & 1) != 0;
+
+ ShaderIrNode OperA;
+
+ switch (Oper)
+ {
+ case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break;
+ case ShaderOper.Imm: OperA = GetOperImm19_20(OpCode); break;
+ case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break;
+
+ default: throw new ArgumentException(nameof(Oper));
+ }
+
+ OperA = GetAluAbsNeg(OperA, Aa, Na);
+
+ bool Signed = Type >= IntType.S8;
+
+ int Shift = Sel * 8;
+
+ int Size = 8 << ((int)Type & 3);
+
+ ulong Mask = ulong.MaxValue >> (64 - Size);
+
+ int Mask32 = (int)Mask;
+
+ if (Shift != 0)
+ {
+ OperA = new ShaderIrOp(ShaderIrInst.Asr, OperA, new ShaderIrOperImm(Shift));
+ }
+
+ if (Mask != uint.MaxValue)
+ {
+ OperA = new ShaderIrOp(ShaderIrInst.And, OperA, new ShaderIrOperImm(Mask32));
+ }
+
+ ShaderIrInst Inst = Signed
+ ? ShaderIrInst.Stof
+ : ShaderIrInst.Utof;
+
+ ShaderIrNode Op = new ShaderIrOp(Inst, OperA);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+ }
+
+ public static void Mov32i(ShaderIrBlock Block, long OpCode)
+ {
+ ShaderIrOperImm Imm = GetOperImm32_20(OpCode);
+
+ Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode));
+ }
+
+ private static IntType GetIntType(long OpCode)
+ {
+ bool Signed = ((OpCode >> 13) & 1) != 0;
+
+ IntType Type = (IntType)((OpCode >> 10) & 3);
+
+ if (Signed)
+ {
+ Type += (int)IntType.S8;
+ }
+
+ return Type;
+ }
+
+ private static FloatType GetFloatType(long OpCode)
+ {
+ return (FloatType)((OpCode >> 8) & 3);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
new file mode 100644
index 00000000..779bbf92
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
@@ -0,0 +1,41 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static class ShaderDecoder
+ {
+ public static ShaderIrBlock DecodeBasicBlock(int[] Code, int Offset, GalShaderType ShaderType)
+ {
+ ShaderIrBlock Block = new ShaderIrBlock();
+
+ while (Offset + 2 <= Code.Length)
+ {
+ uint Word0 = (uint)Code[Offset++];
+ uint Word1 = (uint)Code[Offset++];
+
+ long OpCode = Word0 | (long)Word1 << 32;
+
+ ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode);
+
+ if (Decode == null)
+ {
+ continue;
+ }
+
+ Decode(Block, OpCode);
+
+ if (Block.GetLastNode() is ShaderIrOp Op && IsFlowChange(Op.Inst))
+ {
+ break;
+ }
+ }
+
+ Block.RunOptimizationPasses(ShaderType);
+
+ return Block;
+ }
+
+ private static bool IsFlowChange(ShaderIrInst Inst)
+ {
+ return Inst == ShaderIrInst.Exit;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs
new file mode 100644
index 00000000..00f8f6a5
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrAsg : ShaderIrNode
+ {
+ public ShaderIrNode Dst { get; set; }
+ public ShaderIrNode Src { get; set; }
+
+ public ShaderIrAsg(ShaderIrNode Dst, ShaderIrNode Src)
+ {
+ this.Dst = Dst;
+ this.Src = Src;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
new file mode 100644
index 00000000..1a96d3be
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrBlock
+ {
+ private List<ShaderIrNode> Nodes;
+
+ public ShaderIrBlock()
+ {
+ Nodes = new List<ShaderIrNode>();
+ }
+
+ public void AddNode(ShaderIrNode Node)
+ {
+ Nodes.Add(Node);
+ }
+
+ public void RunOptimizationPasses(GalShaderType ShaderType)
+ {
+ ShaderOptExprProp.Optimize(Nodes, ShaderType);
+ }
+
+ public ShaderIrNode[] GetNodes()
+ {
+ return Nodes.ToArray();
+ }
+
+ public ShaderIrNode GetLastNode()
+ {
+ if (Nodes.Count > 0)
+ {
+ return Nodes[Nodes.Count - 1];
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs
new file mode 100644
index 00000000..d8c87b49
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrCond : ShaderIrNode
+ {
+ public ShaderIrNode Pred { get; set; }
+ public ShaderIrNode Child { get; set; }
+
+ public ShaderIrCond(ShaderIrNode Pred, ShaderIrNode Child)
+ {
+ this.Pred = Pred;
+ this.Child = Child;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
new file mode 100644
index 00000000..b6f4e80b
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
@@ -0,0 +1,59 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ enum ShaderIrInst
+ {
+ B_Start,
+ Band,
+ Bnot,
+ Bor,
+ Bxor,
+ Clt,
+ Ceq,
+ Cle,
+ Cgt,
+ Cne,
+ Cge,
+ Cnum,
+ Cnan,
+ Cltu,
+ Cequ,
+ Cleu,
+ Cgtu,
+ Cneu,
+ Cgeu,
+ B_End,
+
+ F_Start,
+ Fabs,
+ Fadd,
+ Fcos,
+ Fex2,
+ Ffma,
+ Flg2,
+ Fmul,
+ Fneg,
+ Frcp,
+ Frsq,
+ Fsin,
+ Ipa,
+ Texr,
+ Texg,
+ Texb,
+ Texa,
+ F_End,
+
+ I_Start,
+ And,
+ Asr,
+ Lsr,
+ Not,
+ Or,
+ Stof,
+ Utof,
+ Xor,
+ I_End,
+
+ Exit,
+ Kil
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs
new file mode 100644
index 00000000..2648164a
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrNode { }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs
new file mode 100644
index 00000000..cd210757
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrOp : ShaderIrNode
+ {
+ public ShaderIrInst Inst { get; private set; }
+ public ShaderIrNode OperandA { get; set; }
+ public ShaderIrNode OperandB { get; set; }
+ public ShaderIrNode OperandC { get; set; }
+
+ public ShaderIrOp(
+ ShaderIrInst Inst,
+ ShaderIrNode OperandA = null,
+ ShaderIrNode OperandB = null,
+ ShaderIrNode OperandC = null)
+ {
+ this.Inst = Inst;
+ this.OperandA = OperandA;
+ this.OperandB = OperandB;
+ this.OperandC = OperandC;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs
new file mode 100644
index 00000000..fa612de7
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrOperAbuf : ShaderIrNode
+ {
+ public int Offs { get; private set; }
+ public int GprIndex { get; private set; }
+
+ public ShaderIrOperAbuf(int Offs, int GprIndex)
+ {
+ this.Offs = Offs;
+ this.GprIndex = GprIndex;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs
new file mode 100644
index 00000000..f2272056
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrOperCbuf : ShaderIrNode
+ {
+ public int Index { get; private set; }
+ public int Offs { get; private set; }
+
+ public ShaderIrOperCbuf(int Index, int Offs)
+ {
+ this.Index = Index;
+ this.Offs = Offs;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs
new file mode 100644
index 00000000..5c69d6a6
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrOperGpr : ShaderIrNode
+ {
+ public const int ZRIndex = 0xff;
+
+ public bool IsConst => Index == ZRIndex;
+
+ public int Index { get; set; }
+
+ public ShaderIrOperGpr(int Index)
+ {
+ this.Index = Index;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs
new file mode 100644
index 00000000..ba2c2c9b
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrOperImm : ShaderIrNode
+ {
+ public int Value { get; private set; }
+
+ public ShaderIrOperImm(int Value)
+ {
+ this.Value = Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs
new file mode 100644
index 00000000..3c27e483
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrOperImmf : ShaderIrNode
+ {
+ public float Value { get; private set; }
+
+ public ShaderIrOperImmf(float Value)
+ {
+ this.Value = Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs
new file mode 100644
index 00000000..74cca0ef
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ class ShaderIrOperPred : ShaderIrNode
+ {
+ public const int UnusedIndex = 0x7;
+ public const int NeverExecute = 0xf;
+
+ public bool IsConst => Index >= UnusedIndex;
+
+ public int Index { get; set; }
+
+ public ShaderIrOperPred(int Index)
+ {
+ this.Index = Index;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
new file mode 100644
index 00000000..48c3b2ee
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
@@ -0,0 +1,97 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static class ShaderOpCodeTable
+ {
+ private const int EncodingBits = 14;
+
+ private static ShaderDecodeFunc[] OpCodes;
+
+ static ShaderOpCodeTable()
+ {
+ OpCodes = new ShaderDecodeFunc[1 << EncodingBits];
+
+#region Instructions
+ Set("111000110000xx", ShaderDecode.Exit);
+ Set("0100110001011x", ShaderDecode.Fadd_C);
+ Set("0011100x01011x", ShaderDecode.Fadd_I);
+ Set("0101110001011x", ShaderDecode.Fadd_R);
+ Set("010010011xxxxx", ShaderDecode.Ffma_CR);
+ Set("001100101xxxxx", ShaderDecode.Ffma_I);
+ Set("010100011xxxxx", ShaderDecode.Ffma_RC);
+ Set("010110011xxxxx", ShaderDecode.Ffma_RR);
+ Set("0100110001101x", ShaderDecode.Fmul_C);
+ Set("0011100x01101x", ShaderDecode.Fmul_I);
+ Set("0101110001101x", ShaderDecode.Fmul_R);
+ Set("010010111011xx", ShaderDecode.Fsetp_C);
+ Set("0011011x1011xx", ShaderDecode.Fsetp_I);
+ Set("010110111011xx", ShaderDecode.Fsetp_R);
+ Set("0100110010111x", ShaderDecode.I2f_C);
+ Set("0011100x10111x", ShaderDecode.I2f_I);
+ Set("0101110010111x", ShaderDecode.I2f_R);
+ Set("11100000xxxxxx", ShaderDecode.Ipa);
+ Set("111000110011xx", ShaderDecode.Kil);
+ Set("1110111111011x", ShaderDecode.Ld_A);
+ Set("000001xxxxxxxx", ShaderDecode.Lop32i);
+ Set("000000010000xx", ShaderDecode.Mov32i);
+ Set("0101000010000x", ShaderDecode.Mufu);
+ Set("0100110000101x", ShaderDecode.Shr_C);
+ Set("0011100x00101x", ShaderDecode.Shr_I);
+ Set("0101110000101x", ShaderDecode.Shr_R);
+ Set("1110111111110x", ShaderDecode.St_A);
+ Set("1101100xxxxxxx", ShaderDecode.Texs);
+#endregion
+ }
+
+ private static void Set(string Encoding, ShaderDecodeFunc Func)
+ {
+ if (Encoding.Length != EncodingBits)
+ {
+ throw new ArgumentException(nameof(Encoding));
+ }
+
+ int Bit = Encoding.Length - 1;
+ int Value = 0;
+ int XMask = 0;
+ int XBits = 0;
+
+ int[] XPos = new int[Encoding.Length];
+
+ for (int Index = 0; Index < Encoding.Length; Index++, Bit--)
+ {
+ char Chr = Encoding[Index];
+
+ if (Chr == '1')
+ {
+ Value |= 1 << Bit;
+ }
+ else if (Chr == 'x')
+ {
+ XMask |= 1 << Bit;
+
+ XPos[XBits++] = Bit;
+ }
+ }
+
+ XMask = ~XMask;
+
+ for (int Index = 0; Index < (1 << XBits); Index++)
+ {
+ Value &= XMask;
+
+ for (int X = 0; X < XBits; X++)
+ {
+ Value |= ((Index >> X) & 1) << XPos[X];
+ }
+
+ OpCodes[Value] = Func;
+ }
+ }
+
+ public static ShaderDecodeFunc GetDecoder(long OpCode)
+ {
+ return OpCodes[(ulong)OpCode >> (64 - EncodingBits)];
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs
new file mode 100644
index 00000000..7989deed
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ enum ShaderOper
+ {
+ CR,
+ RC,
+ RR,
+ Imm,
+ Immf
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs
new file mode 100644
index 00000000..69457aeb
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+ static class ShaderOptExprProp
+ {
+ private struct UseSite
+ {
+ public object Parent;
+
+ public int OperIndex;
+
+ public UseSite(object Parent, int OperIndex)
+ {
+ this.Parent = Parent;
+ this.OperIndex = OperIndex;
+ }
+ }
+
+ private class RegUse
+ {
+ public ShaderIrAsg Asg { get; private set; }
+
+ public int AsgIndex { get; private set; }
+
+ private bool Propagate;
+
+ private List<UseSite> Sites;
+
+ public RegUse()
+ {
+ Sites = new List<UseSite>();
+ }
+
+ public void AddUseSite(UseSite Site)
+ {
+ Sites.Add(Site);
+ }
+
+ public bool TryPropagate()
+ {
+ //This happens when a untiliazied register is used,
+ //this usually indicates a decoding error, but may also
+ //be cased by bogus programs (?). In any case, we just
+ //keep the unitialized access and avoid trying to propagate
+ //the expression (since we can't propagate what doesn't yet exist).
+ if (Asg == null || !Propagate)
+ {
+ return false;
+ }
+
+ if (Sites.Count > 0)
+ {
+ foreach (UseSite Site in Sites)
+ {
+ if (Site.Parent is ShaderIrCond Cond)
+ {
+ switch (Site.OperIndex)
+ {
+ case 0: Cond.Pred = Asg.Src; break;
+ case 1: Cond.Child = Asg.Src; break;
+
+ default: throw new InvalidOperationException();
+ }
+ }
+ else if (Site.Parent is ShaderIrOp Op)
+ {
+ switch (Site.OperIndex)
+ {
+ case 0: Op.OperandA = Asg.Src; break;
+ case 1: Op.OperandB = Asg.Src; break;
+ case 2: Op.OperandC = Asg.Src; break;
+
+ default: throw new InvalidOperationException();
+ }
+ }
+ else if (Site.Parent is ShaderIrAsg SiteAsg)
+ {
+ SiteAsg.Src = Asg.Src;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public void SetNewAsg(ShaderIrAsg Asg, int AsgIndex, bool Propagate)
+ {
+ this.Asg = Asg;
+ this.AsgIndex = AsgIndex;
+ this.Propagate = Propagate;
+
+ Sites.Clear();
+ }
+ }
+
+ public static void Optimize(List<ShaderIrNode> Nodes, GalShaderType ShaderType)
+ {
+ Dictionary<int, RegUse> Uses = new Dictionary<int, RegUse>();
+
+ RegUse GetUse(int Key)
+ {
+ RegUse Use;
+
+ if (!Uses.TryGetValue(Key, out Use))
+ {
+ Use = new RegUse();
+
+ Uses.Add(Key, Use);
+ }
+
+ return Use;
+ }
+
+ int GetGprKey(int GprIndex)
+ {
+ return GprIndex;
+ }
+
+ int GetPredKey(int PredIndex)
+ {
+ return PredIndex | 0x10000000;
+ }
+
+ RegUse GetGprUse(int GprIndex)
+ {
+ return GetUse(GetGprKey(GprIndex));
+ }
+
+ RegUse GetPredUse(int PredIndex)
+ {
+ return GetUse(GetPredKey(PredIndex));
+ }
+
+ void FindRegUses(List<(int, UseSite)> UseList, object Parent, ShaderIrNode Node, int OperIndex = 0)
+ {
+ if (Node is ShaderIrAsg Asg)
+ {
+ FindRegUses(UseList, Asg, Asg.Src);
+ }
+ else if (Node is ShaderIrCond Cond)
+ {
+ FindRegUses(UseList, Cond, Cond.Pred, 0);
+ FindRegUses(UseList, Cond, Cond.Child, 1);
+ }
+ else if (Node is ShaderIrOp Op)
+ {
+ FindRegUses(UseList, Op, Op.OperandA, 0);
+ FindRegUses(UseList, Op, Op.OperandB, 1);
+ FindRegUses(UseList, Op, Op.OperandC, 2);
+ }
+ else if (Node is ShaderIrOperGpr Gpr && Gpr.Index != ShaderIrOperGpr.ZRIndex)
+ {
+ UseList.Add((GetGprKey(Gpr.Index), new UseSite(Parent, OperIndex)));
+ }
+ else if (Node is ShaderIrOperPred Pred)
+ {
+ UseList.Add((GetPredKey(Pred.Index), new UseSite(Parent, OperIndex)));
+ }
+ }
+
+ void TryAddRegUseSite(ShaderIrNode Node)
+ {
+ List<(int, UseSite)> UseList = new List<(int, UseSite)>();
+
+ FindRegUses(UseList, null, Node);
+
+ foreach ((int Key, UseSite Site) in UseList)
+ {
+ GetUse(Key).AddUseSite(Site);
+ }
+ }
+
+ bool TryPropagate(RegUse Use)
+ {
+ //We can only propagate if the registers that the expression depends
+ //on weren't assigned after the original expression assignment
+ //to a register took place. We traverse the expression tree to find
+ //all registers being used, if any of those registers was assigned
+ //after the assignment to be propagated, then we can't propagate.
+ if (Use?.Asg == null)
+ {
+ return false;
+ }
+
+ List<(int, UseSite)> UseList = new List<(int, UseSite)>();
+
+ FindRegUses(UseList, Use.Asg, Use.Asg.Src);
+
+ foreach ((int Key, UseSite Site) in UseList)
+ {
+ if (GetUse(Key).AsgIndex >= Use.AsgIndex)
+ {
+ return false;
+ }
+ }
+
+ return Use.TryPropagate();
+ }
+
+ for (int Index = 0, AsgIndex = 0; Index < Nodes.Count; Index++, AsgIndex++)
+ {
+ ShaderIrNode Node = Nodes[Index];
+
+ bool IsConditional = Node is ShaderIrCond;
+
+ TryAddRegUseSite(Node);
+
+ while (Node is ShaderIrCond Cond)
+ {
+ Node = Cond.Child;
+ }
+
+ if (!(Node is ShaderIrAsg Asg))
+ {
+ continue;
+ }
+
+ RegUse Use = null;
+
+ if (Asg.Dst is ShaderIrOperGpr Gpr && Gpr.Index != ShaderIrOperGpr.ZRIndex)
+ {
+ Use = GetGprUse(Gpr.Index);
+ }
+ else if (Asg.Dst is ShaderIrOperPred Pred)
+ {
+ Use = GetPredUse(Pred.Index);
+ }
+
+ if (!IsConditional && TryPropagate(Use))
+ {
+ Nodes.Remove(Use.Asg);
+
+ Index--;
+ }
+
+ //All nodes inside conditional nodes can't be propagated,
+ //as we don't even know if they will be executed to begin with.
+ Use?.SetNewAsg(Asg, AsgIndex, !IsConditional);
+ }
+
+ foreach (RegUse Use in Uses.Values)
+ {
+ //Gprs 0-3 are the color output on fragment shaders,
+ //so we can't remove the last assignments to those registers.
+ if (ShaderType == GalShaderType.Fragment)
+ {
+ if (Use.Asg?.Dst is ShaderIrOperGpr Gpr && Gpr.Index < 4)
+ {
+ continue;
+ }
+ }
+
+ if (TryPropagate(Use))
+ {
+ Nodes.Remove(Use.Asg);
+ }
+ }
+ }
+ }
+} \ No newline at end of file