aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics/Gal/Shader
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics/Gal/Shader')
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslDecl.cs26
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs320
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs613
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs5
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs1
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs18
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs2
7 files changed, 930 insertions, 55 deletions
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
index 43923da7..f7ae34fa 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
@@ -1,3 +1,5 @@
+using Ryujinx.Graphics.Gal.OpenGL;
+using Ryujinx.Graphics.Texture;
using System;
using System.Collections.Generic;
@@ -224,6 +226,7 @@ namespace Ryujinx.Graphics.Gal.Shader
if (Op.Inst == ShaderIrInst.Texq ||
Op.Inst == ShaderIrInst.Texs ||
+ Op.Inst == ShaderIrInst.Tld4 ||
Op.Inst == ShaderIrInst.Txlf)
{
int Handle = ((ShaderIrOperImm)Op.OperandC).Value;
@@ -232,7 +235,25 @@ namespace Ryujinx.Graphics.Gal.Shader
string Name = StagePrefix + TextureName + Index;
- m_Textures.TryAdd(Handle, new ShaderDeclInfo(Name, Handle));
+ GalTextureTarget TextureTarget;
+
+ TextureInstructionSuffix TextureInstructionSuffix;
+
+ // TODO: Non 2D texture type for TEXQ?
+ if (Op.Inst == ShaderIrInst.Texq)
+ {
+ TextureTarget = GalTextureTarget.TwoD;
+ TextureInstructionSuffix = TextureInstructionSuffix.None;
+ }
+ else
+ {
+ ShaderIrMetaTex Meta = ((ShaderIrMetaTex)Op.MetaData);
+
+ TextureTarget = Meta.TextureTarget;
+ TextureInstructionSuffix = Meta.TextureInstructionSuffix;
+ }
+
+ m_Textures.TryAdd(Handle, new ShaderDeclInfo(Name, Handle, false, 0, 1, TextureTarget, TextureInstructionSuffix));
}
else if (Op.Inst == ShaderIrInst.Texb)
{
@@ -257,9 +278,10 @@ namespace Ryujinx.Graphics.Gal.Shader
if (HandleSrc != null && HandleSrc is ShaderIrOperCbuf Cbuf)
{
+ ShaderIrMetaTex Meta = ((ShaderIrMetaTex)Op.MetaData);
string Name = StagePrefix + TextureName + "_cb" + Cbuf.Index + "_" + Cbuf.Pos;
- m_CbTextures.Add(Op, new ShaderDeclInfo(Name, Cbuf.Pos, true, Cbuf.Index));
+ m_CbTextures.Add(Op, new ShaderDeclInfo(Name, Cbuf.Pos, true, Cbuf.Index, 1, Meta.TextureTarget, Meta.TextureInstructionSuffix));
}
else
{
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
index 854c827e..5f809525 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
@@ -1,3 +1,5 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.Texture;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -33,7 +35,9 @@ namespace Ryujinx.Graphics.Gal.Shader
public int MaxUboSize { get; }
- public GlslDecompiler(int MaxUboSize)
+ private bool IsNvidiaDriver;
+
+ public GlslDecompiler(int MaxUboSize, bool IsNvidiaDriver)
{
InstsExpr = new Dictionary<ShaderIrInst, GetInstExpr>()
{
@@ -103,6 +107,7 @@ namespace Ryujinx.Graphics.Gal.Shader
{ ShaderIrInst.Texb, GetTexbExpr },
{ ShaderIrInst.Texq, GetTexqExpr },
{ ShaderIrInst.Texs, GetTexsExpr },
+ { ShaderIrInst.Tld4, GetTld4Expr },
{ ShaderIrInst.Trunc, GetTruncExpr },
{ ShaderIrInst.Txlf, GetTxlfExpr },
{ ShaderIrInst.Utof, GetUtofExpr },
@@ -110,6 +115,7 @@ namespace Ryujinx.Graphics.Gal.Shader
};
this.MaxUboSize = MaxUboSize / 16;
+ this.IsNvidiaDriver = IsNvidiaDriver;
}
public GlslProgram Decompile(
@@ -219,14 +225,70 @@ namespace Ryujinx.Graphics.Gal.Shader
}
}
+ private string GetSamplerType(TextureTarget TextureTarget, bool HasShadow)
+ {
+ string Result;
+
+ switch (TextureTarget)
+ {
+ case TextureTarget.Texture1D:
+ Result = "sampler1D";
+ break;
+ case TextureTarget.Texture2D:
+ Result = "sampler2D";
+ break;
+ case TextureTarget.Texture3D:
+ Result = "sampler3D";
+ break;
+ case TextureTarget.TextureCubeMap:
+ Result = "samplerCube";
+ break;
+ case TextureTarget.TextureRectangle:
+ Result = "sampler2DRect";
+ break;
+ case TextureTarget.Texture1DArray:
+ Result = "sampler1DArray";
+ break;
+ case TextureTarget.Texture2DArray:
+ Result = "sampler2DArray";
+ break;
+ case TextureTarget.TextureCubeMapArray:
+ Result = "samplerCubeArray";
+ break;
+ case TextureTarget.TextureBuffer:
+ Result = "samplerBuffer";
+ break;
+ case TextureTarget.Texture2DMultisample:
+ Result = "sampler2DMS";
+ break;
+ case TextureTarget.Texture2DMultisampleArray:
+ Result = "sampler2DMSArray";
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+
+ if (HasShadow)
+ Result += "Shadow";
+
+ return Result;
+ }
+
private void PrintDeclTextures()
{
foreach (ShaderDeclInfo DeclInfo in IterateCbTextures())
{
- SB.AppendLine("uniform sampler2D " + DeclInfo.Name + ";");
+ TextureTarget Target = ImageUtils.GetTextureTarget(DeclInfo.TextureTarget);
+ SB.AppendLine($"// {DeclInfo.TextureSuffix}");
+ SB.AppendLine("uniform " + GetSamplerType(Target, (DeclInfo.TextureSuffix & TextureInstructionSuffix.DC) != 0) + " " + DeclInfo.Name + ";");
}
- PrintDecls(Decl.Textures, "uniform sampler2D");
+ foreach (ShaderDeclInfo DeclInfo in Decl.Textures.Values.OrderBy(DeclKeySelector))
+ {
+ TextureTarget Target = ImageUtils.GetTextureTarget(DeclInfo.TextureTarget);
+ SB.AppendLine($"// {DeclInfo.TextureSuffix}");
+ SB.AppendLine("uniform " + GetSamplerType(Target, (DeclInfo.TextureSuffix & TextureInstructionSuffix.DC) != 0) + " " + DeclInfo.Name + ";");
+ }
}
private IEnumerable<ShaderDeclInfo> IterateCbTextures()
@@ -778,6 +840,7 @@ namespace Ryujinx.Graphics.Gal.Shader
case ShaderIrInst.Ipa:
case ShaderIrInst.Texq:
case ShaderIrInst.Texs:
+ case ShaderIrInst.Tld4:
case ShaderIrInst.Txlf:
return false;
}
@@ -1124,7 +1187,7 @@ namespace Ryujinx.Graphics.Gal.Shader
string Ch = "rgba".Substring(Meta.Elem, 1);
- return "texture(" + DeclInfo.Name + ", " + Coords + ")." + Ch;
+ return GetTextureOperation(Op, DeclInfo.Name, Coords, Ch);
}
private string GetTexqExpr(ShaderIrOp Op)
@@ -1157,20 +1220,50 @@ namespace Ryujinx.Graphics.Gal.Shader
string Ch = "rgba".Substring(Meta.Elem, 1);
- return "texture(" + Sampler + ", " + Coords + ")." + Ch;
+ return GetTextureOperation(Op, Sampler, Coords, Ch);
+ }
+
+ private string GetTld4Expr(ShaderIrOp Op)
+ {
+ ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData;
+
+ string Sampler = GetTexSamplerName(Op);
+
+ string Coords = GetTexSamplerCoords(Op);
+
+ string Ch = "rgba".Substring(Meta.Elem, 1);
+
+ return GetTextureGatherOperation(Op, Sampler, Coords, Ch);
}
+ // TODO: support AOFFI on non nvidia drivers
private string GetTxlfExpr(ShaderIrOp Op)
{
+ // TODO: Support all suffixes
ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData;
+ TextureInstructionSuffix Suffix = Meta.TextureInstructionSuffix;
+
string Sampler = GetTexSamplerName(Op);
string Coords = GetITexSamplerCoords(Op);
string Ch = "rgba".Substring(Meta.Elem, 1);
- return "texelFetch(" + Sampler + ", " + Coords + ", 0)." + Ch;
+ string Lod = "0";
+
+ if (Meta.LevelOfDetail != null)
+ {
+ Lod = GetOperExpr(Op, Meta.LevelOfDetail);
+ }
+
+ if ((Suffix & TextureInstructionSuffix.AOffI) != 0 && IsNvidiaDriver)
+ {
+ string Offset = GetTextureOffset(Meta, GetOperExpr(Op, Meta.Offset));
+ return "texelFetchOffset(" + Sampler + ", " + Coords + ", " + Lod + ", " + Offset + ")." + Ch;
+ }
+
+ return "texelFetch(" + Sampler + ", " + Coords + ", " + Lod + ")." + Ch;
}
private string GetTruncExpr(ShaderIrOp Op) => GetUnaryCall(Op, "trunc");
@@ -1246,14 +1339,205 @@ namespace Ryujinx.Graphics.Gal.Shader
private string GetTexSamplerCoords(ShaderIrOp Op)
{
- return "vec2(" + GetOperExpr(Op, Op.OperandA) + ", " +
- GetOperExpr(Op, Op.OperandB) + ")";
+ ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData;
+
+ bool HasDepth = (Meta.TextureInstructionSuffix & TextureInstructionSuffix.DC) != 0;
+
+ int Coords = ImageUtils.GetCoordsCountTextureTarget(Meta.TextureTarget);
+
+ bool IsArray = ImageUtils.IsArray(Meta.TextureTarget);
+
+
+ string GetLastArgument(ShaderIrNode Node)
+ {
+ string Result = GetOperExpr(Op, Node);
+
+ // array index is actually an integer so we need to pass it correctly
+ if (IsArray)
+ {
+ Result = "float(floatBitsToInt(" + Result + "))";
+ }
+
+ return Result;
+ }
+
+ string LastArgument;
+ string DepthArgument = "";
+
+ int VecSize = Coords;
+ if (HasDepth && Op.Inst != ShaderIrInst.Tld4)
+ {
+ VecSize++;
+ DepthArgument = $", {GetOperExpr(Op, Meta.DepthCompare)}";
+ }
+
+ switch (Coords)
+ {
+ case 1:
+ if (HasDepth)
+ {
+ return $"vec3({GetOperExpr(Op, Meta.Coordinates[0])}, 0.0{DepthArgument})";
+ }
+
+ return GetOperExpr(Op, Meta.Coordinates[0]);
+ case 2:
+ LastArgument = GetLastArgument(Meta.Coordinates[1]);
+
+ return $"vec{VecSize}({GetOperExpr(Op, Meta.Coordinates[0])}, {LastArgument}{DepthArgument})";
+ case 3:
+ LastArgument = GetLastArgument(Meta.Coordinates[2]);
+
+ return $"vec{VecSize}({GetOperExpr(Op, Meta.Coordinates[0])}, {GetOperExpr(Op, Meta.Coordinates[1])}, {LastArgument}{DepthArgument})";
+ case 4:
+ LastArgument = GetLastArgument(Meta.Coordinates[3]);
+
+ return $"vec4({GetOperExpr(Op, Meta.Coordinates[0])}, {GetOperExpr(Op, Meta.Coordinates[1])}, {GetOperExpr(Op, Meta.Coordinates[2])}, {LastArgument}){DepthArgument}";
+ default:
+ throw new InvalidOperationException();
+ }
+
+ }
+
+ private string GetTextureOffset(ShaderIrMetaTex Meta, string Oper, int Shift = 4, int Mask = 0xF)
+ {
+ string GetOffset(string Operation, int Index)
+ {
+ return $"({Operation} >> {Index * Shift}) & 0x{Mask:x}";
+ }
+
+ int Coords = ImageUtils.GetCoordsCountTextureTarget(Meta.TextureTarget);
+
+ if (ImageUtils.IsArray(Meta.TextureTarget))
+ Coords -= 1;
+
+ switch (Coords)
+ {
+ case 1:
+ return GetOffset(Oper, 0);
+ case 2:
+ return "ivec2(" + GetOffset(Oper, 0) + ", " + GetOffset(Oper, 1) + ")";
+ case 3:
+ return "ivec3(" + GetOffset(Oper, 0) + ", " + GetOffset(Oper, 1) + ", " + GetOffset(Oper, 2) + ")";
+ case 4:
+ return "ivec4(" + GetOffset(Oper, 0) + ", " + GetOffset(Oper, 1) + ", " + GetOffset(Oper, 2) + ", " + GetOffset(Oper, 3) + ")";
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ // TODO: support AOFFI on non nvidia drivers
+ private string GetTextureGatherOperation(ShaderIrOp Op, string Sampler, string Coords, string Ch)
+ {
+ ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData;
+
+ TextureInstructionSuffix Suffix = Meta.TextureInstructionSuffix;
+
+ string ChString = "." + Ch;
+
+ string Comp = Meta.Component.ToString();
+
+ if ((Suffix & TextureInstructionSuffix.DC) != 0)
+ {
+ Comp = GetOperExpr(Op, Meta.DepthCompare);
+ }
+
+ if ((Suffix & TextureInstructionSuffix.AOffI) != 0 && IsNvidiaDriver)
+ {
+ string Offset = GetTextureOffset(Meta, "floatBitsToInt((" + GetOperExpr(Op, Meta.Offset) + "))", 8, 0x3F);
+
+ if ((Suffix & TextureInstructionSuffix.DC) != 0)
+ {
+ return "textureGatherOffset(" + Sampler + ", " + Coords + ", " + Comp + ", " + Offset + ")" + ChString;
+ }
+
+ return "textureGatherOffset(" + Sampler + ", " + Coords + ", " + Offset + ", " + Comp + ")" + ChString;
+ }
+ // TODO: Support PTP
+ else if ((Suffix & TextureInstructionSuffix.PTP) != 0)
+ {
+ throw new NotImplementedException();
+ }
+
+ return "textureGather(" + Sampler + ", " + Coords + ", " + Comp + ")" + ChString;
+ }
+
+ // TODO: support AOFFI on non nvidia drivers
+ private string GetTextureOperation(ShaderIrOp Op, string Sampler, string Coords, string Ch)
+ {
+ ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData;
+
+ TextureInstructionSuffix Suffix = Meta.TextureInstructionSuffix;
+
+ string ChString = "." + Ch;
+
+ if ((Suffix & TextureInstructionSuffix.DC) != 0)
+ {
+ ChString = "";
+ }
+
+ // TODO: Support LBA and LLA
+ if ((Suffix & TextureInstructionSuffix.LZ) != 0)
+ {
+ if ((Suffix & TextureInstructionSuffix.AOffI) != 0 && IsNvidiaDriver)
+ {
+ string Offset = GetTextureOffset(Meta, "floatBitsToInt((" + GetOperExpr(Op, Meta.Offset) + "))");
+
+ return "textureLodOffset(" + Sampler + ", " + Coords + ", 0.0, " + Offset + ")" + ChString;
+ }
+
+ return "textureLod(" + Sampler + ", " + Coords + ", 0.0)" + ChString;
+ }
+ else if ((Suffix & TextureInstructionSuffix.LB) != 0)
+ {
+ if ((Suffix & TextureInstructionSuffix.AOffI) != 0 && IsNvidiaDriver)
+ {
+ string Offset = GetTextureOffset(Meta, "floatBitsToInt((" + GetOperExpr(Op, Meta.Offset) + "))");
+
+ return "textureOffset(" + Sampler + ", " + Coords + ", " + Offset + ", " + GetOperExpr(Op, Meta.LevelOfDetail) + ")" + ChString;
+ }
+
+ return "texture(" + Sampler + ", " + Coords + ", " + GetOperExpr(Op, Meta.LevelOfDetail) + ")" + ChString;
+ }
+ else if ((Suffix & TextureInstructionSuffix.LL) != 0)
+ {
+ if ((Suffix & TextureInstructionSuffix.AOffI) != 0 && IsNvidiaDriver)
+ {
+ string Offset = GetTextureOffset(Meta, "floatBitsToInt((" + GetOperExpr(Op, Meta.Offset) + "))");
+
+ return "textureLodOffset(" + Sampler + ", " + Coords + ", " + GetOperExpr(Op, Meta.LevelOfDetail) + ", " + Offset + ")" + ChString;
+ }
+
+ return "textureLod(" + Sampler + ", " + Coords + ", " + GetOperExpr(Op, Meta.LevelOfDetail) + ")" + ChString;
+ }
+ else if ((Suffix & TextureInstructionSuffix.AOffI) != 0 && IsNvidiaDriver)
+ {
+ string Offset = GetTextureOffset(Meta, "floatBitsToInt((" + GetOperExpr(Op, Meta.Offset) + "))");
+
+ return "textureOffset(" + Sampler + ", " + Coords + ", " + Offset + ")" + ChString;
+ }
+ else
+ {
+ return "texture(" + Sampler + ", " + Coords + ")" + ChString;
+ }
+ throw new NotImplementedException($"Texture Suffix {Meta.TextureInstructionSuffix} is not implemented");
+
}
private string GetITexSamplerCoords(ShaderIrOp Op)
{
- return "ivec2(" + GetOperExpr(Op, Op.OperandA) + ", " +
- GetOperExpr(Op, Op.OperandB) + ")";
+ ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData;
+
+ switch (ImageUtils.GetCoordsCountTextureTarget(Meta.TextureTarget))
+ {
+ case 1:
+ return GetOperExpr(Op, Meta.Coordinates[0]);
+ case 2:
+ return "ivec2(" + GetOperExpr(Op, Meta.Coordinates[0]) + ", " + GetOperExpr(Op, Meta.Coordinates[1]) + ")";
+ case 3:
+ return "ivec3(" + GetOperExpr(Op, Meta.Coordinates[0]) + ", " + GetOperExpr(Op, Meta.Coordinates[1]) + ", " + GetOperExpr(Op, Meta.Coordinates[2]) + ")";
+ default:
+ throw new InvalidOperationException();
+ }
}
private string GetOperExpr(ShaderIrOp Op, ShaderIrNode Oper)
@@ -1292,22 +1576,6 @@ namespace Ryujinx.Graphics.Gal.Shader
}
break;
}
-
- case ShaderIrOperImm Imm:
- {
- //For integer immediates being used as float,
- //it's better (for readability) to just return the float value.
- if (DstType == OperType.F32)
- {
- float Value = BitConverter.Int32BitsToSingle(Imm.Value);
-
- if (!float.IsNaN(Value) && !float.IsInfinity(Value))
- {
- return GetFloatConst(Value);
- }
- }
- break;
- }
}
switch (DstType)
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
index adcc47b9..8b4eacdf 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Graphics.Texture;
using System;
using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
@@ -29,6 +30,75 @@ namespace Ryujinx.Graphics.Gal.Shader
{ RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ }
};
+ private static GalTextureTarget TexToTextureTarget(int TexType, bool IsArray)
+ {
+ switch (TexType)
+ {
+ case 0:
+ return IsArray ? GalTextureTarget.OneDArray : GalTextureTarget.OneD;
+ case 2:
+ return IsArray ? GalTextureTarget.TwoDArray : GalTextureTarget.TwoD;
+ case 4:
+ if (IsArray)
+ throw new InvalidOperationException($"ARRAY bit set on a TEX with 3D texture!");
+ return GalTextureTarget.ThreeD;
+ case 6:
+ return IsArray ? GalTextureTarget.CubeArray : GalTextureTarget.CubeMap;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ private static GalTextureTarget TexsToTextureTarget(int TexType)
+ {
+ switch (TexType)
+ {
+ case 0:
+ return GalTextureTarget.OneD;
+ case 2:
+ case 4:
+ case 6:
+ case 8:
+ case 0xa:
+ case 0xc:
+ return GalTextureTarget.TwoD;
+ case 0xe:
+ case 0x10:
+ case 0x12:
+ return GalTextureTarget.TwoDArray;
+ case 0x14:
+ case 0x16:
+ return GalTextureTarget.ThreeD;
+ case 0x18:
+ case 0x1a:
+ return GalTextureTarget.CubeMap;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ public static GalTextureTarget TldsToTextureTarget(int TexType)
+ {
+ switch (TexType)
+ {
+ case 0:
+ case 2:
+ return GalTextureTarget.OneD;
+ case 4:
+ case 8:
+ case 0xa:
+ case 0xc:
+ case 0x18:
+ return GalTextureTarget.TwoD;
+ case 0x10:
+ return GalTextureTarget.TwoDArray;
+ case 0xe:
+ return GalTextureTarget.ThreeD;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
public static void Ld_A(ShaderIrBlock Block, long OpCode, int Position)
{
ShaderIrNode[] Opers = OpCode.Abuf20();
@@ -132,43 +202,166 @@ namespace Ryujinx.Graphics.Gal.Shader
public static void Tex(ShaderIrBlock Block, long OpCode, int Position)
{
- EmitTex(Block, OpCode, GprHandle: false);
+ TextureInstructionSuffix Suffix;
+
+ int RawSuffix = OpCode.Read(0x34, 0x38);
+
+ switch (RawSuffix)
+ {
+ case 0:
+ Suffix = TextureInstructionSuffix.None;
+ break;
+ case 0x8:
+ Suffix = TextureInstructionSuffix.LZ;
+ break;
+ case 0x10:
+ Suffix = TextureInstructionSuffix.LB;
+ break;
+ case 0x18:
+ Suffix = TextureInstructionSuffix.LL;
+ break;
+ case 0x30:
+ Suffix = TextureInstructionSuffix.LBA;
+ break;
+ case 0x38:
+ Suffix = TextureInstructionSuffix.LLA;
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid Suffix for TEX instruction {RawSuffix}");
+ }
+
+ bool IsOffset = OpCode.Read(0x36);
+
+ if (IsOffset)
+ Suffix |= TextureInstructionSuffix.AOffI;
+
+ EmitTex(Block, OpCode, Suffix, GprHandle: false);
}
public static void Tex_B(ShaderIrBlock Block, long OpCode, int Position)
{
- EmitTex(Block, OpCode, GprHandle: true);
+ TextureInstructionSuffix Suffix;
+
+ int RawSuffix = OpCode.Read(0x24, 0xe);
+
+ switch (RawSuffix)
+ {
+ case 0:
+ Suffix = TextureInstructionSuffix.None;
+ break;
+ case 0x2:
+ Suffix = TextureInstructionSuffix.LZ;
+ break;
+ case 0x4:
+ Suffix = TextureInstructionSuffix.LB;
+ break;
+ case 0x6:
+ Suffix = TextureInstructionSuffix.LL;
+ break;
+ case 0xc:
+ Suffix = TextureInstructionSuffix.LBA;
+ break;
+ case 0xe:
+ Suffix = TextureInstructionSuffix.LLA;
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid Suffix for TEX.B instruction {RawSuffix}");
+ }
+
+ bool IsOffset = OpCode.Read(0x23);
+
+ if (IsOffset)
+ Suffix |= TextureInstructionSuffix.AOffI;
+
+ EmitTex(Block, OpCode, Suffix, GprHandle: true);
}
- private static void EmitTex(ShaderIrBlock Block, long OpCode, bool GprHandle)
+ private static void EmitTex(ShaderIrBlock Block, long OpCode, TextureInstructionSuffix TextureInstructionSuffix, bool GprHandle)
{
- //TODO: Support other formats.
- ShaderIrOperGpr[] Coords = new ShaderIrOperGpr[2];
+ bool IsArray = OpCode.HasArray();
+
+ GalTextureTarget TextureTarget = TexToTextureTarget(OpCode.Read(28, 6), IsArray);
+
+ bool HasDepthCompare = OpCode.Read(0x32);
+
+ if (HasDepthCompare)
+ {
+ TextureInstructionSuffix |= TextureInstructionSuffix.DC;
+ }
- for (int Index = 0; Index < Coords.Length; Index++)
+ ShaderIrOperGpr[] Coords = new ShaderIrOperGpr[ImageUtils.GetCoordsCountTextureTarget(TextureTarget)];
+
+ int IndexExtraCoord = 0;
+
+ if (IsArray)
+ {
+ IndexExtraCoord++;
+
+ Coords[Coords.Length - 1] = OpCode.Gpr8();
+ }
+
+
+ for (int Index = 0; Index < Coords.Length - IndexExtraCoord; Index++)
{
ShaderIrOperGpr CoordReg = OpCode.Gpr8();
CoordReg.Index += Index;
+ CoordReg.Index += IndexExtraCoord;
+
if (!CoordReg.IsValidRegister)
{
CoordReg.Index = ShaderIrOperGpr.ZRIndex;
}
- Coords[Index] = ShaderIrOperGpr.MakeTemporary(Index);
-
- Block.AddNode(new ShaderIrAsg(Coords[Index], CoordReg));
+ Coords[Index] = CoordReg;
}
int ChMask = OpCode.Read(31, 0xf);
+ ShaderIrOperGpr LevelOfDetail = null;
+ ShaderIrOperGpr Offset = null;
+ ShaderIrOperGpr DepthCompare = null;
+
+ // TODO: determine first argument when TEX.B is used
+ int OperBIndex = GprHandle ? 1 : 0;
+
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.LL) != 0 ||
+ (TextureInstructionSuffix & TextureInstructionSuffix.LB) != 0 ||
+ (TextureInstructionSuffix & TextureInstructionSuffix.LBA) != 0 ||
+ (TextureInstructionSuffix & TextureInstructionSuffix.LLA) != 0)
+ {
+ LevelOfDetail = OpCode.Gpr20();
+ LevelOfDetail.Index += OperBIndex;
+
+ OperBIndex++;
+ }
+
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.AOffI) != 0)
+ {
+ Offset = OpCode.Gpr20();
+ Offset.Index += OperBIndex;
+
+ OperBIndex++;
+ }
+
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.DC) != 0)
+ {
+ DepthCompare = OpCode.Gpr20();
+ DepthCompare.Index += OperBIndex;
+
+ OperBIndex++;
+ }
+
+ // ???
ShaderIrNode OperC = GprHandle
? (ShaderIrNode)OpCode.Gpr20()
: (ShaderIrNode)OpCode.Imm13_36();
ShaderIrInst Inst = GprHandle ? ShaderIrInst.Texb : ShaderIrInst.Texs;
+ Coords = CoordsRegistersToTempRegisters(Block, Coords);
+
int RegInc = 0;
for (int Ch = 0; Ch < 4; Ch++)
@@ -187,9 +380,14 @@ namespace Ryujinx.Graphics.Gal.Shader
continue;
}
- ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch);
+ ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch, TextureTarget, TextureInstructionSuffix, Coords)
+ {
+ LevelOfDetail = LevelOfDetail,
+ Offset = Offset,
+ DepthCompare = DepthCompare
+ };
- ShaderIrOp Op = new ShaderIrOp(Inst, Coords[0], Coords[1], OperC, Meta);
+ ShaderIrOp Op = new ShaderIrOp(Inst, Coords[0], Coords.Length > 1 ? Coords[1] : null, OperC, Meta);
Block.AddNode(OpCode.PredNode(new ShaderIrAsg(Dst, Op)));
}
@@ -197,17 +395,238 @@ namespace Ryujinx.Graphics.Gal.Shader
public static void Texs(ShaderIrBlock Block, long OpCode, int Position)
{
- EmitTexs(Block, OpCode, ShaderIrInst.Texs);
+ TextureInstructionSuffix Suffix;
+
+ int RawSuffix = OpCode.Read(0x34, 0x1e);
+
+ switch (RawSuffix)
+ {
+ case 0:
+ case 0x4:
+ case 0x10:
+ case 0x16:
+ Suffix = TextureInstructionSuffix.LZ;
+ break;
+ case 0x6:
+ case 0x1a:
+ Suffix = TextureInstructionSuffix.LL;
+ break;
+ case 0x8:
+ Suffix = TextureInstructionSuffix.DC;
+ break;
+ case 0x2:
+ case 0xe:
+ case 0x14:
+ case 0x18:
+ Suffix = TextureInstructionSuffix.None;
+ break;
+ case 0xa:
+ Suffix = TextureInstructionSuffix.LL | TextureInstructionSuffix.DC;
+ break;
+ case 0xc:
+ case 0x12:
+ Suffix = TextureInstructionSuffix.LZ | TextureInstructionSuffix.DC;
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid Suffix for TEXS instruction {RawSuffix}");
+ }
+
+ GalTextureTarget TextureTarget = TexsToTextureTarget(OpCode.Read(52, 0x1e));
+
+ EmitTexs(Block, OpCode, ShaderIrInst.Texs, TextureTarget, Suffix);
}
public static void Tlds(ShaderIrBlock Block, long OpCode, int Position)
{
- EmitTexs(Block, OpCode, ShaderIrInst.Txlf);
+ TextureInstructionSuffix Suffix;
+
+ int RawSuffix = OpCode.Read(0x34, 0x1e);
+
+ switch (RawSuffix)
+ {
+ case 0:
+ case 0x4:
+ case 0x8:
+ Suffix = TextureInstructionSuffix.LZ | TextureInstructionSuffix.AOffI;
+ break;
+ case 0xc:
+ Suffix = TextureInstructionSuffix.LZ | TextureInstructionSuffix.MZ;
+ break;
+ case 0xe:
+ case 0x10:
+ Suffix = TextureInstructionSuffix.LZ;
+ break;
+ case 0x2:
+ case 0xa:
+ Suffix = TextureInstructionSuffix.LL;
+ break;
+ case 0x18:
+ Suffix = TextureInstructionSuffix.LL | TextureInstructionSuffix.AOffI;
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid Suffix for TLDS instruction {RawSuffix}");
+ }
+
+ GalTextureTarget TextureTarget = TldsToTextureTarget(OpCode.Read(52, 0x1e));
+
+ EmitTexs(Block, OpCode, ShaderIrInst.Txlf, TextureTarget, Suffix);
+ }
+
+ public static void Tld4(ShaderIrBlock Block, long OpCode, int Position)
+ {
+ TextureInstructionSuffix Suffix;
+
+ int RawSuffix = OpCode.Read(0x34, 0xc);
+
+ switch (RawSuffix)
+ {
+ case 0:
+ Suffix = TextureInstructionSuffix.None;
+ break;
+ case 0x4:
+ Suffix = TextureInstructionSuffix.AOffI;
+ break;
+ case 0x8:
+ Suffix = TextureInstructionSuffix.PTP;
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid Suffix for TLD4 instruction {RawSuffix}");
+ }
+
+ bool IsShadow = OpCode.Read(0x32);
+
+ bool IsArray = OpCode.HasArray();
+ int ChMask = OpCode.Read(31, 0xf);
+
+ GalTextureTarget TextureTarget = TexToTextureTarget(OpCode.Read(28, 6), IsArray);
+
+ if (IsShadow)
+ {
+ Suffix |= TextureInstructionSuffix.DC;
+ }
+
+ EmitTld4(Block, OpCode, TextureTarget, Suffix, ChMask, OpCode.Read(0x38, 0x3), false);
+ }
+
+ public static void Tld4s(ShaderIrBlock Block, long OpCode, int Position)
+ {
+ TextureInstructionSuffix Suffix = TextureInstructionSuffix.None;
+
+ bool IsOffset = OpCode.Read(0x33);
+ bool IsShadow = OpCode.Read(0x32);
+
+ if (IsOffset)
+ {
+ Suffix |= TextureInstructionSuffix.AOffI;
+ }
+
+ if (IsShadow)
+ {
+ Suffix |= TextureInstructionSuffix.DC;
+ }
+
+ // TLD4S seems to only support 2D textures with RGBA mask?
+ EmitTld4(Block, OpCode, GalTextureTarget.TwoD, Suffix, RGBA, OpCode.Read(0x34, 0x3), true);
}
- private static void EmitTexs(ShaderIrBlock Block, long OpCode, ShaderIrInst Inst)
+ private static void EmitTexs(ShaderIrBlock Block,
+ long OpCode,
+ ShaderIrInst Inst,
+ GalTextureTarget TextureTarget,
+ TextureInstructionSuffix TextureInstructionSuffix)
{
- //TODO: Support other formats.
+ if (Inst == ShaderIrInst.Txlf && TextureTarget == GalTextureTarget.CubeArray)
+ {
+ throw new InvalidOperationException("TLDS instructions cannot use CUBE modifier!");
+ }
+
+ bool IsArray = ImageUtils.IsArray(TextureTarget);
+
+ ShaderIrOperGpr[] Coords = new ShaderIrOperGpr[ImageUtils.GetCoordsCountTextureTarget(TextureTarget)];
+
+ ShaderIrOperGpr OperA = OpCode.Gpr8();
+ ShaderIrOperGpr OperB = OpCode.Gpr20();
+
+ ShaderIrOperGpr SuffixExtra = OpCode.Gpr20();
+ SuffixExtra.Index += 1;
+
+ int CoordStartIndex = 0;
+
+ if (IsArray)
+ {
+ CoordStartIndex++;
+ Coords[Coords.Length - 1] = OpCode.Gpr8();
+ }
+
+ switch (Coords.Length - CoordStartIndex)
+ {
+ case 1:
+ Coords[0] = OpCode.Gpr8();
+
+ break;
+ case 2:
+ Coords[0] = OpCode.Gpr8();
+ Coords[0].Index += CoordStartIndex;
+
+ break;
+ case 3:
+ Coords[0] = OpCode.Gpr8();
+ Coords[0].Index += CoordStartIndex;
+
+ Coords[1] = OpCode.Gpr8();
+ Coords[1].Index += 1 + CoordStartIndex;
+
+ break;
+ default:
+ throw new NotSupportedException($"{Coords.Length - CoordStartIndex} coords textures aren't supported in TEXS");
+ }
+
+ int OperBIndex = 0;
+
+ ShaderIrOperGpr LevelOfDetail = null;
+ ShaderIrOperGpr Offset = null;
+ ShaderIrOperGpr DepthCompare = null;
+
+ // OperB is always the last value
+ // Not applicable to 1d textures
+ if (Coords.Length - CoordStartIndex != 1)
+ {
+ Coords[Coords.Length - CoordStartIndex - 1] = OperB;
+ OperBIndex++;
+ }
+
+ // Encoding of TEXS/TLDS is a bit special and change for 2d textures
+ // NOTE: OperA seems to hold at best two args.
+ // On 2D textures, if no suffix need an additional values, Y is stored in OperB, otherwise coords are in OperA and the additional values is in OperB.
+ if (TextureInstructionSuffix != TextureInstructionSuffix.None && TextureInstructionSuffix != TextureInstructionSuffix.LZ && TextureTarget == GalTextureTarget.TwoD)
+ {
+ Coords[Coords.Length - CoordStartIndex - 1] = OpCode.Gpr8();
+ Coords[Coords.Length - CoordStartIndex - 1].Index += Coords.Length - CoordStartIndex - 1;
+ OperBIndex--;
+ }
+
+ // TODO: Find what MZ does and what changes about the encoding (Maybe Multisample?)
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.LL) != 0)
+ {
+ LevelOfDetail = OpCode.Gpr20();
+ LevelOfDetail.Index += OperBIndex;
+ OperBIndex++;
+ }
+
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.AOffI) != 0)
+ {
+ Offset = OpCode.Gpr20();
+ Offset.Index += OperBIndex;
+ OperBIndex++;
+ }
+
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.DC) != 0)
+ {
+ DepthCompare = OpCode.Gpr20();
+ DepthCompare.Index += OperBIndex;
+ OperBIndex++;
+ }
+
int LutIndex;
LutIndex = !OpCode.Gpr0().IsConst ? 1 : 0;
@@ -276,12 +695,7 @@ namespace Ryujinx.Graphics.Gal.Shader
}
ShaderIrNode OperC = OpCode.Imm13_36();
-
- ShaderIrOperGpr Coord0 = ShaderIrOperGpr.MakeTemporary(0);
- ShaderIrOperGpr Coord1 = ShaderIrOperGpr.MakeTemporary(1);
-
- Block.AddNode(new ShaderIrAsg(Coord0, OpCode.Gpr8()));
- Block.AddNode(new ShaderIrAsg(Coord1, OpCode.Gpr20()));
+ Coords = CoordsRegistersToTempRegisters(Block, Coords);
for (int Ch = 0; Ch < 4; Ch++)
{
@@ -290,9 +704,13 @@ namespace Ryujinx.Graphics.Gal.Shader
continue;
}
- ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch);
-
- ShaderIrOp Op = new ShaderIrOp(Inst, Coord0, Coord1, OperC, Meta);
+ ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch, TextureTarget, TextureInstructionSuffix, Coords)
+ {
+ LevelOfDetail = LevelOfDetail,
+ Offset = Offset,
+ DepthCompare = DepthCompare
+ };
+ ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB, OperC, Meta);
ShaderIrOperGpr Dst = GetDst();
@@ -303,9 +721,156 @@ namespace Ryujinx.Graphics.Gal.Shader
}
}
+ private static void EmitTld4(ShaderIrBlock Block, long OpCode, GalTextureTarget TextureType, TextureInstructionSuffix TextureInstructionSuffix, int ChMask, int Component, bool Scalar)
+ {
+ ShaderIrOperGpr OperA = OpCode.Gpr8();
+ ShaderIrOperGpr OperB = OpCode.Gpr20();
+ ShaderIrOperImm OperC = OpCode.Imm13_36();
+
+ ShaderIrOperGpr[] Coords = new ShaderIrOperGpr[ImageUtils.GetCoordsCountTextureTarget(TextureType)];
+
+ ShaderIrOperGpr Offset = null;
+ ShaderIrOperGpr DepthCompare = null;
+
+ bool IsArray = ImageUtils.IsArray(TextureType);
+
+ int OperBIndex = 0;
+
+ if (Scalar)
+ {
+ int CoordStartIndex = 0;
+
+ if (IsArray)
+ {
+ CoordStartIndex++;
+ Coords[Coords.Length - 1] = OperB;
+ }
+
+ switch (Coords.Length - CoordStartIndex)
+ {
+ case 1:
+ Coords[0] = OpCode.Gpr8();
+
+ break;
+ case 2:
+ Coords[0] = OpCode.Gpr8();
+ Coords[0].Index += CoordStartIndex;
+
+ break;
+ case 3:
+ Coords[0] = OpCode.Gpr8();
+ Coords[0].Index += CoordStartIndex;
+
+ Coords[1] = OpCode.Gpr8();
+ Coords[1].Index += 1 + CoordStartIndex;
+
+ break;
+ default:
+ throw new NotSupportedException($"{Coords.Length - CoordStartIndex} coords textures aren't supported in TLD4S");
+ }
+
+ if (Coords.Length - CoordStartIndex != 1)
+ {
+ Coords[Coords.Length - CoordStartIndex - 1] = OperB;
+ OperBIndex++;
+ }
+
+ if (TextureInstructionSuffix != TextureInstructionSuffix.None && TextureType == GalTextureTarget.TwoD)
+ {
+ Coords[Coords.Length - CoordStartIndex - 1] = OpCode.Gpr8();
+ Coords[Coords.Length - CoordStartIndex - 1].Index += Coords.Length - CoordStartIndex - 1;
+ OperBIndex--;
+ }
+ }
+ else
+ {
+ int IndexExtraCoord = 0;
+
+ if (IsArray)
+ {
+ IndexExtraCoord++;
+
+ Coords[Coords.Length - 1] = OpCode.Gpr8();
+ }
+
+ for (int Index = 0; Index < Coords.Length - IndexExtraCoord; Index++)
+ {
+ Coords[Index] = OpCode.Gpr8();
+
+ Coords[Index].Index += Index;
+
+ Coords[Index].Index += IndexExtraCoord;
+
+ if (Coords[Index].Index > ShaderIrOperGpr.ZRIndex)
+ {
+ Coords[Index].Index = ShaderIrOperGpr.ZRIndex;
+ }
+ }
+ }
+
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.AOffI) != 0)
+ {
+ Offset = OpCode.Gpr20();
+ Offset.Index += OperBIndex;
+ OperBIndex++;
+ }
+
+ if ((TextureInstructionSuffix & TextureInstructionSuffix.DC) != 0)
+ {
+ DepthCompare = OpCode.Gpr20();
+ DepthCompare.Index += OperBIndex;
+ OperBIndex++;
+ }
+
+ Coords = CoordsRegistersToTempRegisters(Block, Coords);
+
+ int RegInc = 0;
+
+ for (int Ch = 0; Ch < 4; Ch++)
+ {
+ if (!IsChannelUsed(ChMask, Ch))
+ {
+ continue;
+ }
+
+ ShaderIrOperGpr Dst = OpCode.Gpr0();
+
+ Dst.Index += RegInc++;
+
+ if (!Dst.IsValidRegister || Dst.IsConst)
+ {
+ continue;
+ }
+
+ ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch, TextureType, TextureInstructionSuffix, Coords)
+ {
+ Component = Component,
+ Offset = Offset,
+ DepthCompare = DepthCompare
+ };
+
+ ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Tld4, OperA, OperB, OperC, Meta);
+
+ Block.AddNode(OpCode.PredNode(new ShaderIrAsg(Dst, Op)));
+ }
+ }
+
private static bool IsChannelUsed(int ChMask, int Ch)
{
return (ChMask & (1 << Ch)) != 0;
}
+
+ private static ShaderIrOperGpr[] CoordsRegistersToTempRegisters(ShaderIrBlock Block, params ShaderIrOperGpr[] Registers)
+ {
+ ShaderIrOperGpr[] Res = new ShaderIrOperGpr[Registers.Length];
+
+ for (int Index = 0; Index < Res.Length; Index++)
+ {
+ Res[Index] = ShaderIrOperGpr.MakeTemporary(Index);
+ Block.AddNode(new ShaderIrAsg(Res[Index], Registers[Index]));
+ }
+
+ return Res;
+ }
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs
index f0f92148..e241e1ca 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs
@@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gal.Shader
return ((int)(OpCode >> 20) << 8) >> 8;
}
+ private static bool HasArray(this long OpCode)
+ {
+ return OpCode.Read(0x1c);
+ }
+
private static ShaderIrOperAbuf[] Abuf20(this long OpCode)
{
int Abuf = OpCode.Read(20, 0x3ff);
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
index 35dea612..68ff214e 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
@@ -49,6 +49,7 @@ namespace Ryujinx.Graphics.Gal.Shader
Ipa,
Texb,
Texs,
+ Tld4,
Trunc,
F_End,
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs
index 82f3bb77..72ea221a 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs
@@ -1,12 +1,24 @@
+using Ryujinx.Graphics.Texture;
+
namespace Ryujinx.Graphics.Gal.Shader
{
class ShaderIrMetaTex : ShaderIrMeta
{
- public int Elem { get; private set; }
+ public int Elem { get; private set; }
+ public GalTextureTarget TextureTarget { get; private set; }
+ public ShaderIrNode[] Coordinates { get; private set; }
+ public TextureInstructionSuffix TextureInstructionSuffix { get; private set; }
+ public ShaderIrOperGpr LevelOfDetail;
+ public ShaderIrOperGpr Offset;
+ public ShaderIrOperGpr DepthCompare;
+ public int Component; // for TLD4(S)
- public ShaderIrMetaTex(int Elem)
+ public ShaderIrMetaTex(int Elem, GalTextureTarget TextureTarget, TextureInstructionSuffix TextureInstructionSuffix, params ShaderIrNode[] Coordinates)
{
- this.Elem = Elem;
+ this.Elem = Elem;
+ this.TextureTarget = TextureTarget;
+ this.TextureInstructionSuffix = TextureInstructionSuffix;
+ this.Coordinates = Coordinates;
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
index 177e36c3..d2bbd38c 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
@@ -122,6 +122,8 @@ namespace Ryujinx.Graphics.Gal.Shader
Set("1101111101001x", ShaderDecode.Texq);
Set("1101x00xxxxxxx", ShaderDecode.Texs);
Set("1101101xxxxxxx", ShaderDecode.Tlds);
+ Set("110010xxxx111x", ShaderDecode.Tld4);
+ Set("1101111100xxxx", ShaderDecode.Tld4s);
Set("01011111xxxxxx", ShaderDecode.Vmad);
Set("0100111xxxxxxx", ShaderDecode.Xmad_CR);
Set("0011011x00xxxx", ShaderDecode.Xmad_I);