diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs index 99cd4d762..6a9abe75b 100644 --- a/Ryujinx.Graphics/Gal/IGalShader.cs +++ b/Ryujinx.Graphics/Gal/IGalShader.cs @@ -1,3 +1,4 @@ +using Ryujinx.Graphics.Shader; using System.Collections.Generic; namespace Ryujinx.Graphics.Gal @@ -8,8 +9,8 @@ namespace Ryujinx.Graphics.Gal void Create(IGalMemory memory, long vpAPos, long key, GalShaderType type); - IEnumerable GetConstBufferUsage(long key); - IEnumerable GetTextureUsage(long key); + IEnumerable GetConstBufferUsage(long key); + IEnumerable GetTextureUsage(long key); void Bind(long key); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs index 3c8ada3ea..64768e285 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs @@ -1,4 +1,5 @@ using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.Shader; using System; using System.Collections.Generic; @@ -529,9 +530,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL { if (stage != null) { - foreach (ShaderDeclInfo declInfo in stage.ConstBufferUsage) + foreach (CBufferDescriptor desc in stage.ConstBufferUsage) { - long key = New.ConstBufferKeys[(int)stage.Type][declInfo.Cbuf]; + long key = New.ConstBufferKeys[(int)stage.Type][desc.Slot]; if (key != 0 && _buffer.TryGetUbo(key, out int uboHandle)) { diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs index 8faa90537..8f4072c53 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs @@ -1,5 +1,6 @@ using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Gal.Shader; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -51,54 +52,54 @@ namespace Ryujinx.Graphics.Gal.OpenGL bool isDualVp, GalShaderType type) { - GlslProgram program; + ShaderConfig config = new ShaderConfig(type, OglLimit.MaxUboSize); - GlslDecompiler decompiler = new GlslDecompiler(OglLimit.MaxUboSize, OglExtension.NvidiaDriver); - - int shaderDumpIndex = ShaderDumper.DumpIndex; + ShaderProgram program; if (isDualVp) { ShaderDumper.Dump(memory, position, type, "a"); ShaderDumper.Dump(memory, positionB, type, "b"); - program = decompiler.Decompile(memory, position, positionB, type); + program = Translator.Translate(memory, (ulong)position, (ulong)positionB, config); } else { ShaderDumper.Dump(memory, position, type); - program = decompiler.Decompile(memory, position, type); + program = Translator.Translate(memory, (ulong)position, config); } string code = program.Code; if (ShaderDumper.IsDumpEnabled()) { + int shaderDumpIndex = ShaderDumper.DumpIndex; + code = "//Shader " + shaderDumpIndex + Environment.NewLine + code; } - return new OglShaderStage(type, code, program.Uniforms, program.Textures); + return new OglShaderStage(type, code, program.Info.CBuffers, program.Info.Textures); } - public IEnumerable GetConstBufferUsage(long key) + public IEnumerable GetConstBufferUsage(long key) { if (_stages.TryGetValue(key, out OglShaderStage stage)) { return stage.ConstBufferUsage; } - return Enumerable.Empty(); + return Enumerable.Empty(); } - public IEnumerable GetTextureUsage(long key) + public IEnumerable GetTextureUsage(long key) { if (_stages.TryGetValue(key, out OglShaderStage stage)) { return stage.TextureUsage; } - return Enumerable.Empty(); + return Enumerable.Empty(); } public unsafe void SetExtraData(float flipX, float flipY, int instance) @@ -130,16 +131,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL private void Bind(OglShaderStage stage) { - if (stage.Type == GalShaderType.Geometry) - { - //Enhanced layouts are required for Geometry shaders - //skip this stage if current driver has no ARB_enhanced_layouts - if (!OglExtension.EnhancedLayouts) - { - return; - } - } - switch (stage.Type) { case GalShaderType.Vertex: Current.Vertex = stage; break; @@ -221,7 +212,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL private void BindUniformBlocks(int programHandle) { - int extraBlockindex = GL.GetUniformBlockIndex(programHandle, GlslDecl.ExtraUniformBlockName); + int extraBlockindex = GL.GetUniformBlockIndex(programHandle, "Extra"); GL.UniformBlockBinding(programHandle, extraBlockindex, 0); @@ -231,14 +222,16 @@ namespace Ryujinx.Graphics.Gal.OpenGL { if (stage != null) { - foreach (ShaderDeclInfo declInfo in stage.ConstBufferUsage) + foreach (CBufferDescriptor desc in stage.ConstBufferUsage) { - int blockIndex = GL.GetUniformBlockIndex(programHandle, declInfo.Name); + int blockIndex = GL.GetUniformBlockIndex(programHandle, desc.Name); if (blockIndex < 0) { - //It is expected that its found, if it's not then driver might be in a malfunction - throw new InvalidOperationException(); + //This may be fine, the compiler may optimize away unused uniform buffers, + //and in this case the above call would return -1 as the buffer has been + //optimized away. + continue; } GL.UniformBlockBinding(programHandle, blockIndex, freeBinding); @@ -263,9 +256,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL { if (stage != null) { - foreach (ShaderDeclInfo decl in stage.TextureUsage) + foreach (TextureDescriptor desc in stage.TextureUsage) { - int location = GL.GetUniformLocation(programHandle, decl.Name); + int location = GL.GetUniformLocation(programHandle, desc.Name); GL.Uniform1(location, index); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs b/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs index 9e68a8e6d..86126bca4 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs @@ -1,4 +1,5 @@ using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.Shader; using System; using System.Collections.Generic; @@ -23,14 +24,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL public string Code { get; private set; } - public IEnumerable ConstBufferUsage { get; private set; } - public IEnumerable TextureUsage { get; private set; } + public IEnumerable ConstBufferUsage { get; private set; } + public IEnumerable TextureUsage { get; private set; } public OglShaderStage( - GalShaderType type, - string code, - IEnumerable constBufferUsage, - IEnumerable textureUsage) + GalShaderType type, + string code, + IEnumerable constBufferUsage, + IEnumerable textureUsage) { Type = type; Code = code; diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs deleted file mode 100644 index 734267625..000000000 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs +++ /dev/null @@ -1,420 +0,0 @@ -using Ryujinx.Graphics.Texture; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - class GlslDecl - { - public const int LayerAttr = 0x064; - public const int PointSizeAttr = 0x06c; - public const int PointCoordAttrX = 0x2e0; - public const int PointCoordAttrY = 0x2e4; - public const int TessCoordAttrX = 0x2f0; - public const int TessCoordAttrY = 0x2f4; - public const int TessCoordAttrZ = 0x2f8; - public const int InstanceIdAttr = 0x2f8; - public const int VertexIdAttr = 0x2fc; - public const int FaceAttr = 0x3fc; - - public const int GlPositionVec4Index = 7; - - public const int PositionOutAttrLocation = 15; - - private const int AttrStartIndex = 8; - private const int TexStartIndex = 8; - - public const string PositionOutAttrName = "position"; - - private const string TextureName = "tex"; - private const string UniformName = "c"; - - private const string AttrName = "attr"; - private const string InAttrName = "in_" + AttrName; - private const string OutAttrName = "out_" + AttrName; - - private const string GprName = "gpr"; - private const string PredName = "pred"; - - public const string FragmentOutputName = "FragColor"; - - public const string ExtraUniformBlockName = "Extra"; - public const string FlipUniformName = "flip"; - public const string InstanceUniformName = "instance"; - - public const string BasicBlockName = "bb"; - public const string BasicBlockAName = BasicBlockName + "_a"; - public const string BasicBlockBName = BasicBlockName + "_b"; - - public const int SsyStackSize = 16; - public const string SsyStackName = "ssy_stack"; - public const string SsyCursorName = "ssy_cursor"; - - private string[] _stagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; - - private string _stagePrefix; - - private Dictionary m_CbTextures; - - private Dictionary m_Textures; - private Dictionary m_Uniforms; - - private Dictionary m_Attributes; - private Dictionary m_InAttributes; - private Dictionary m_OutAttributes; - - private Dictionary m_Gprs; - private Dictionary m_GprsHalf; - private Dictionary m_Preds; - - public IReadOnlyDictionary CbTextures => m_CbTextures; - - public IReadOnlyDictionary Textures => m_Textures; - public IReadOnlyDictionary Uniforms => m_Uniforms; - - public IReadOnlyDictionary Attributes => m_Attributes; - public IReadOnlyDictionary InAttributes => m_InAttributes; - public IReadOnlyDictionary OutAttributes => m_OutAttributes; - - public IReadOnlyDictionary Gprs => m_Gprs; - public IReadOnlyDictionary GprsHalf => m_GprsHalf; - public IReadOnlyDictionary Preds => m_Preds; - - public GalShaderType ShaderType { get; private set; } - - private GlslDecl(GalShaderType shaderType) - { - ShaderType = shaderType; - - m_CbTextures = new Dictionary(); - - m_Textures = new Dictionary(); - m_Uniforms = new Dictionary(); - - m_Attributes = new Dictionary(); - m_InAttributes = new Dictionary(); - m_OutAttributes = new Dictionary(); - - m_Gprs = new Dictionary(); - m_GprsHalf = new Dictionary(); - m_Preds = new Dictionary(); - } - - public GlslDecl(ShaderIrBlock[] blocks, GalShaderType shaderType, ShaderHeader header) : this(shaderType) - { - _stagePrefix = _stagePrefixes[(int)shaderType] + "_"; - - if (shaderType == GalShaderType.Fragment) - { - int index = 0; - - for (int attachment = 0; attachment < 8; attachment++) - { - for (int component = 0; component < 4; component++) - { - if (header.OmapTargets[attachment].ComponentEnabled(component)) - { - m_Gprs.TryAdd(index, new ShaderDeclInfo(GetGprName(index), index)); - - index++; - } - } - } - - if (header.OmapDepth) - { - index = header.DepthRegister; - - m_Gprs.TryAdd(index, new ShaderDeclInfo(GetGprName(index), index)); - } - } - - foreach (ShaderIrBlock block in blocks) - { - ShaderIrNode[] nodes = block.GetNodes(); - - foreach (ShaderIrNode node in nodes) - { - Traverse(nodes, null, node); - } - } - } - - public static GlslDecl Merge(GlslDecl vpA, GlslDecl vpB) - { - GlslDecl combined = new GlslDecl(GalShaderType.Vertex); - - Merge(combined.m_Textures, vpA.m_Textures, vpB.m_Textures); - Merge(combined.m_Uniforms, vpA.m_Uniforms, vpB.m_Uniforms); - - Merge(combined.m_Attributes, vpA.m_Attributes, vpB.m_Attributes); - Merge(combined.m_OutAttributes, vpA.m_OutAttributes, vpB.m_OutAttributes); - - Merge(combined.m_Gprs, vpA.m_Gprs, vpB.m_Gprs); - Merge(combined.m_GprsHalf, vpA.m_GprsHalf, vpB.m_GprsHalf); - Merge(combined.m_Preds, vpA.m_Preds, vpB.m_Preds); - - //Merge input attributes. - foreach (KeyValuePair kv in vpA.m_InAttributes) - { - combined.m_InAttributes.TryAdd(kv.Key, kv.Value); - } - - foreach (KeyValuePair kv in vpB.m_InAttributes) - { - //If Vertex Program A already writes to this attribute, - //then we don't need to add it as an input attribute since - //Vertex Program A will already have written to it anyway, - //and there's no guarantee that there is an input attribute - //for this slot. - if (!vpA.m_OutAttributes.ContainsKey(kv.Key)) - { - combined.m_InAttributes.TryAdd(kv.Key, kv.Value); - } - } - - return combined; - } - - public static string GetGprName(int index) - { - return GprName + index; - } - - private static void Merge( - Dictionary c, - Dictionary a, - Dictionary b) - { - foreach (KeyValuePair kv in a) - { - c.TryAdd(kv.Key, kv.Value); - } - - foreach (KeyValuePair kv in b) - { - c.TryAdd(kv.Key, kv.Value); - } - } - - private void Traverse(ShaderIrNode[] nodes, ShaderIrNode parent, ShaderIrNode node) - { - switch (node) - { - case ShaderIrAsg asg: - { - Traverse(nodes, asg, asg.Dst); - Traverse(nodes, asg, asg.Src); - - break; - } - - case ShaderIrCond cond: - { - Traverse(nodes, cond, cond.Pred); - Traverse(nodes, cond, cond.Child); - - break; - } - - case ShaderIrOp op: - { - Traverse(nodes, op, op.OperandA); - Traverse(nodes, op, op.OperandB); - Traverse(nodes, op, op.OperandC); - - if (op.Inst == ShaderIrInst.Texq || - op.Inst == ShaderIrInst.Texs || - op.Inst == ShaderIrInst.Tld4 || - op.Inst == ShaderIrInst.Txlf) - { - int handle = ((ShaderIrOperImm)op.OperandC).Value; - - int index = handle - TexStartIndex; - - string name = _stagePrefix + TextureName + index; - - 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) - { - ShaderIrNode handleSrc = null; - - int index = Array.IndexOf(nodes, parent) - 1; - - for (; index >= 0; index--) - { - ShaderIrNode curr = nodes[index]; - - if (curr is ShaderIrAsg asg && asg.Dst is ShaderIrOperGpr gpr) - { - if (gpr.Index == ((ShaderIrOperGpr)op.OperandC).Index) - { - handleSrc = asg.Src; - - break; - } - } - } - - 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, 1, meta.TextureTarget, meta.TextureInstructionSuffix)); - } - else - { - throw new NotImplementedException("Shader TEX.B instruction is not fully supported!"); - } - } - break; - } - - case ShaderIrOperCbuf cbuf: - { - if (!m_Uniforms.ContainsKey(cbuf.Index)) - { - string name = _stagePrefix + UniformName + cbuf.Index; - - ShaderDeclInfo declInfo = new ShaderDeclInfo(name, cbuf.Pos, true, cbuf.Index); - - m_Uniforms.Add(cbuf.Index, declInfo); - } - break; - } - - case ShaderIrOperAbuf abuf: - { - //This is a built-in variable. - if (abuf.Offs == LayerAttr || - abuf.Offs == PointSizeAttr || - abuf.Offs == PointCoordAttrX || - abuf.Offs == PointCoordAttrY || - abuf.Offs == VertexIdAttr || - abuf.Offs == InstanceIdAttr || - abuf.Offs == FaceAttr) - { - break; - } - - int index = abuf.Offs >> 4; - int elem = (abuf.Offs >> 2) & 3; - - int glslIndex = index - AttrStartIndex; - - if (glslIndex < 0) - { - return; - } - - 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); - - if (!m_Attributes.ContainsKey(index)) - { - declInfo = new ShaderDeclInfo(AttrName + glslIndex, glslIndex, false, 0, 4); - - m_Attributes.Add(index, declInfo); - } - - Traverse(nodes, abuf, abuf.Vertex); - - break; - } - - case ShaderIrOperGpr gpr: - { - if (!gpr.IsConst) - { - string name = GetGprName(gpr.Index); - - if (gpr.RegisterSize == ShaderRegisterSize.Single) - { - m_Gprs.TryAdd(gpr.Index, new ShaderDeclInfo(name, gpr.Index)); - } - else if (gpr.RegisterSize == ShaderRegisterSize.Half) - { - name += "_h" + gpr.HalfPart; - - m_GprsHalf.TryAdd((gpr.Index << 1) | gpr.HalfPart, new ShaderDeclInfo(name, gpr.Index)); - } - else /* if (Gpr.RegisterSize == ShaderRegisterSize.Double) */ - { - throw new NotImplementedException("Double types are not supported."); - } - } - 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 decls, int index) - { - //This is used to check if the dictionary already contains - //a entry for a vector at a given index position. - //Used to enable turning gprs into vectors. - int vecIndex = index & ~3; - - if (decls.TryGetValue(vecIndex, out ShaderDeclInfo declInfo)) - { - if (declInfo.Size > 1 && index < vecIndex + declInfo.Size) - { - return true; - } - } - - return decls.ContainsKey(index); - } - } -} diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs deleted file mode 100644 index 228a90185..000000000 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ /dev/null @@ -1,1679 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Texture; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; - -namespace Ryujinx.Graphics.Gal.Shader -{ - public class GlslDecompiler - { - private delegate string GetInstExpr(ShaderIrOp op); - - private Dictionary _instsExpr; - - private enum OperType - { - Bool, - F32, - I32 - } - - private const string IdentationStr = " "; - - private const int MaxVertexInput = 3; - - private GlslDecl _decl; - - private ShaderHeader _header, _headerB; - - private ShaderIrBlock[] _blocks, _blocksB; - - private StringBuilder _sb; - - public int MaxUboSize { get; } - - private bool _isNvidiaDriver; - - public GlslDecompiler(int maxUboSize, bool isNvidiaDriver) - { - _instsExpr = new Dictionary() - { - { ShaderIrInst.Abs, GetAbsExpr }, - { ShaderIrInst.Add, GetAddExpr }, - { ShaderIrInst.And, GetAndExpr }, - { ShaderIrInst.Asr, GetAsrExpr }, - { ShaderIrInst.Band, GetBandExpr }, - { ShaderIrInst.Bnot, GetBnotExpr }, - { ShaderIrInst.Bor, GetBorExpr }, - { ShaderIrInst.Bxor, GetBxorExpr }, - { ShaderIrInst.Ceil, GetCeilExpr }, - { ShaderIrInst.Ceq, GetCeqExpr }, - { ShaderIrInst.Cge, GetCgeExpr }, - { ShaderIrInst.Cgt, GetCgtExpr }, - { ShaderIrInst.Clamps, GetClampsExpr }, - { ShaderIrInst.Clampu, GetClampuExpr }, - { ShaderIrInst.Cle, GetCleExpr }, - { ShaderIrInst.Clt, GetCltExpr }, - { ShaderIrInst.Cne, GetCneExpr }, - { ShaderIrInst.Cut, GetCutExpr }, - { ShaderIrInst.Exit, GetExitExpr }, - { ShaderIrInst.Fabs, GetAbsExpr }, - { ShaderIrInst.Fadd, GetAddExpr }, - { ShaderIrInst.Fceq, GetCeqExpr }, - { ShaderIrInst.Fcequ, GetCequExpr }, - { ShaderIrInst.Fcge, GetCgeExpr }, - { ShaderIrInst.Fcgeu, GetCgeuExpr }, - { ShaderIrInst.Fcgt, GetCgtExpr }, - { ShaderIrInst.Fcgtu, GetCgtuExpr }, - { ShaderIrInst.Fclamp, GetFclampExpr }, - { ShaderIrInst.Fcle, GetCleExpr }, - { ShaderIrInst.Fcleu, GetCleuExpr }, - { ShaderIrInst.Fclt, GetCltExpr }, - { ShaderIrInst.Fcltu, GetCltuExpr }, - { ShaderIrInst.Fcnan, GetCnanExpr }, - { ShaderIrInst.Fcne, GetCneExpr }, - { ShaderIrInst.Fcneu, GetCneuExpr }, - { ShaderIrInst.Fcnum, GetCnumExpr }, - { ShaderIrInst.Fcos, GetFcosExpr }, - { ShaderIrInst.Fex2, GetFex2Expr }, - { ShaderIrInst.Ffma, GetFfmaExpr }, - { ShaderIrInst.Flg2, GetFlg2Expr }, - { ShaderIrInst.Floor, GetFloorExpr }, - { ShaderIrInst.Fmax, GetMaxExpr }, - { ShaderIrInst.Fmin, GetMinExpr }, - { ShaderIrInst.Fmul, GetMulExpr }, - { ShaderIrInst.Fneg, GetNegExpr }, - { ShaderIrInst.Frcp, GetFrcpExpr }, - { ShaderIrInst.Frsq, GetFrsqExpr }, - { ShaderIrInst.Fsin, GetFsinExpr }, - { ShaderIrInst.Fsqrt, GetFsqrtExpr }, - { ShaderIrInst.Ftos, GetFtosExpr }, - { ShaderIrInst.Ftou, GetFtouExpr }, - { ShaderIrInst.Ipa, GetIpaExpr }, - { ShaderIrInst.Kil, GetKilExpr }, - { ShaderIrInst.Lsl, GetLslExpr }, - { ShaderIrInst.Lsr, GetLsrExpr }, - { ShaderIrInst.Max, GetMaxExpr }, - { ShaderIrInst.Min, GetMinExpr }, - { ShaderIrInst.Mul, GetMulExpr }, - { ShaderIrInst.Neg, GetNegExpr }, - { ShaderIrInst.Not, GetNotExpr }, - { ShaderIrInst.Or, GetOrExpr }, - { ShaderIrInst.Stof, GetStofExpr }, - { ShaderIrInst.Sub, GetSubExpr }, - { ShaderIrInst.Texb, GetTexbExpr }, - { ShaderIrInst.Texq, GetTexqExpr }, - { ShaderIrInst.Texs, GetTexsExpr }, - { ShaderIrInst.Tld4, GetTld4Expr }, - { ShaderIrInst.Trunc, GetTruncExpr }, - { ShaderIrInst.Txlf, GetTxlfExpr }, - { ShaderIrInst.Utof, GetUtofExpr }, - { ShaderIrInst.Xor, GetXorExpr } - }; - - MaxUboSize = maxUboSize / 16; - _isNvidiaDriver = isNvidiaDriver; - } - - public GlslProgram Decompile( - IGalMemory memory, - long vpAPosition, - long vpBPosition, - GalShaderType shaderType) - { - _header = new ShaderHeader(memory, vpAPosition); - _headerB = new ShaderHeader(memory, vpBPosition); - - _blocks = ShaderDecoder.Decode(memory, vpAPosition); - _blocksB = ShaderDecoder.Decode(memory, vpBPosition); - - GlslDecl declVpA = new GlslDecl(_blocks, shaderType, _header); - GlslDecl declVpB = new GlslDecl(_blocksB, shaderType, _headerB); - - _decl = GlslDecl.Merge(declVpA, declVpB); - - return Decompile(); - } - - public GlslProgram Decompile(IGalMemory memory, long position, GalShaderType shaderType) - { - _header = new ShaderHeader(memory, position); - _headerB = null; - - _blocks = ShaderDecoder.Decode(memory, position); - _blocksB = null; - - _decl = new GlslDecl(_blocks, shaderType, _header); - - return Decompile(); - } - - private GlslProgram Decompile() - { - _sb = new StringBuilder(); - - _sb.AppendLine("#version 410 core"); - - PrintDeclHeader(); - PrintDeclTextures(); - PrintDeclUniforms(); - PrintDeclAttributes(); - PrintDeclInAttributes(); - PrintDeclOutAttributes(); - PrintDeclGprs(); - PrintDeclPreds(); - PrintDeclSsy(); - - if (_blocksB != null) - { - PrintBlockScope(_blocks, GlslDecl.BasicBlockAName); - - _sb.AppendLine(); - - PrintBlockScope(_blocksB, GlslDecl.BasicBlockBName); - } - else - { - PrintBlockScope(_blocks, GlslDecl.BasicBlockName); - } - - _sb.AppendLine(); - - PrintMain(); - - string glslCode = _sb.ToString(); - - List textureInfo = new List(); - - textureInfo.AddRange(_decl.Textures.Values); - textureInfo.AddRange(IterateCbTextures()); - - return new GlslProgram(glslCode, textureInfo, _decl.Uniforms.Values); - } - - private void PrintDeclHeader() - { - if (_decl.ShaderType == GalShaderType.Geometry) - { - int maxVertices = _header.MaxOutputVertexCount; - - string outputTopology; - - switch (_header.OutputTopology) - { - case ShaderHeader.PointList: outputTopology = "points"; break; - case ShaderHeader.LineStrip: outputTopology = "line_strip"; break; - case ShaderHeader.TriangleStrip: outputTopology = "triangle_strip"; break; - - default: throw new InvalidOperationException(); - } - - _sb.AppendLine("#extension GL_ARB_enhanced_layouts : require"); - - _sb.AppendLine(); - - _sb.AppendLine("// Stubbed. Maxwell geometry shaders don't inform input geometry type"); - - _sb.AppendLine("layout(triangles) in;" + Environment.NewLine); - - _sb.AppendLine($"layout({outputTopology}, max_vertices = {maxVertices}) out;"); - - _sb.AppendLine(); - } - } - - 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()) - { - TextureTarget target = ImageUtils.GetTextureTarget(declInfo.TextureTarget); - _sb.AppendLine($"// {declInfo.TextureSuffix}"); - _sb.AppendLine("uniform " + GetSamplerType(target, (declInfo.TextureSuffix & TextureInstructionSuffix.Dc) != 0) + " " + declInfo.Name + ";"); - } - - 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 IterateCbTextures() - { - HashSet names = new HashSet(); - - foreach (ShaderDeclInfo declInfo in _decl.CbTextures.Values.OrderBy(DeclKeySelector)) - { - if (names.Add(declInfo.Name)) - { - yield return declInfo; - } - } - } - - private void PrintDeclUniforms() - { - if (_decl.ShaderType == GalShaderType.Vertex) - { - //Memory layout here is [flip_x, flip_y, instance, unused] - //It's using 4 bytes, not 8 - - _sb.AppendLine("layout (std140) uniform " + GlslDecl.ExtraUniformBlockName + " {"); - - _sb.AppendLine(IdentationStr + "vec2 " + GlslDecl.FlipUniformName + ";"); - - _sb.AppendLine(IdentationStr + "int " + GlslDecl.InstanceUniformName + ";"); - - _sb.AppendLine("};"); - _sb.AppendLine(); - } - - foreach (ShaderDeclInfo declInfo in _decl.Uniforms.Values.OrderBy(DeclKeySelector)) - { - _sb.AppendLine($"layout (std140) uniform {declInfo.Name} {{"); - - _sb.AppendLine($"{IdentationStr}vec4 {declInfo.Name}_data[{MaxUboSize}];"); - - _sb.AppendLine("};"); - } - - if (_decl.Uniforms.Count > 0) - { - _sb.AppendLine(); - } - } - - private void PrintDeclAttributes() - { - string geometryArray = (_decl.ShaderType == GalShaderType.Geometry) ? "[" + MaxVertexInput + "]" : ""; - - PrintDecls(_decl.Attributes, suffix: geometryArray); - } - - private void PrintDeclInAttributes() - { - if (_decl.ShaderType == GalShaderType.Fragment) - { - _sb.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") in vec4 " + GlslDecl.PositionOutAttrName + ";"); - } - - if (_decl.ShaderType == GalShaderType.Geometry) - { - if (_decl.InAttributes.Count > 0) - { - _sb.AppendLine("in Vertex {"); - - foreach (ShaderDeclInfo declInfo in _decl.InAttributes.Values.OrderBy(DeclKeySelector)) - { - if (declInfo.Index >= 0) - { - _sb.AppendLine(IdentationStr + "layout (location = " + declInfo.Index + ") vec4 " + declInfo.Name + "; "); - } - } - - _sb.AppendLine("} block_in[];" + Environment.NewLine); - } - } - else - { - PrintDeclAttributes(_decl.InAttributes.Values, "in"); - } - } - - private void PrintDeclOutAttributes() - { - if (_decl.ShaderType == GalShaderType.Fragment) - { - int count = 0; - - for (int attachment = 0; attachment < 8; attachment++) - { - if (_header.OmapTargets[attachment].Enabled) - { - _sb.AppendLine("layout (location = " + attachment + ") out vec4 " + GlslDecl.FragmentOutputName + attachment + ";"); - - count++; - } - } - - if (count > 0) - { - _sb.AppendLine(); - } - } - else - { - _sb.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") out vec4 " + GlslDecl.PositionOutAttrName + ";"); - _sb.AppendLine(); - } - - PrintDeclAttributes(_decl.OutAttributes.Values, "out"); - } - - private void PrintDeclAttributes(IEnumerable decls, string inOut) - { - int count = 0; - - foreach (ShaderDeclInfo declInfo in decls.OrderBy(DeclKeySelector)) - { - if (declInfo.Index >= 0) - { - _sb.AppendLine("layout (location = " + declInfo.Index + ") " + inOut + " vec4 " + declInfo.Name + ";"); - - count++; - } - } - - if (count > 0) - { - _sb.AppendLine(); - } - } - - private void PrintDeclGprs() - { - PrintDecls(_decl.Gprs); - PrintDecls(_decl.GprsHalf); - } - - private void PrintDeclPreds() - { - PrintDecls(_decl.Preds, "bool"); - } - - private void PrintDeclSsy() - { - _sb.AppendLine("uint " + GlslDecl.SsyCursorName + " = 0;"); - - _sb.AppendLine("uint " + GlslDecl.SsyStackName + "[" + GlslDecl.SsyStackSize + "];" + Environment.NewLine); - } - - private void PrintDecls(IReadOnlyDictionary dict, string customType = null, string suffix = "") - { - foreach (ShaderDeclInfo declInfo in dict.Values.OrderBy(DeclKeySelector)) - { - string name; - - if (customType != null) - { - name = customType + " " + declInfo.Name + suffix + ";"; - } - else if (declInfo.Name.Contains(GlslDecl.FragmentOutputName)) - { - name = "layout (location = " + declInfo.Index / 4 + ") out vec4 " + declInfo.Name + suffix + ";"; - } - else - { - name = GetDecl(declInfo) + suffix + ";"; - } - - _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) - { - if (declInfo.Size == 4) - { - return "vec4 " + declInfo.Name; - } - else - { - return "float " + declInfo.Name; - } - } - - private void PrintMain() - { - _sb.AppendLine("void main() {"); - - foreach (KeyValuePair kv in _decl.InAttributes) - { - if (!_decl.Attributes.TryGetValue(kv.Key, out ShaderDeclInfo attr)) - { - continue; - } - - ShaderDeclInfo declInfo = kv.Value; - - if (_decl.ShaderType == GalShaderType.Geometry) - { - for (int vertex = 0; vertex < MaxVertexInput; vertex++) - { - string dst = attr.Name + "[" + vertex + "]"; - - string src = "block_in[" + vertex + "]." + declInfo.Name; - - _sb.AppendLine(IdentationStr + dst + " = " + src + ";"); - } - } - else - { - _sb.AppendLine(IdentationStr + attr.Name + " = " + declInfo.Name + ";"); - } - } - - _sb.AppendLine(IdentationStr + "uint pc;"); - - if (_blocksB != null) - { - PrintProgram(_blocks, GlslDecl.BasicBlockAName); - PrintProgram(_blocksB, GlslDecl.BasicBlockBName); - } - else - { - PrintProgram(_blocks, GlslDecl.BasicBlockName); - } - - if (_decl.ShaderType != GalShaderType.Geometry) - { - PrintAttrToOutput(); - } - - if (_decl.ShaderType == GalShaderType.Fragment) - { - if (_header.OmapDepth) - { - _sb.AppendLine(IdentationStr + "gl_FragDepth = " + GlslDecl.GetGprName(_header.DepthRegister) + ";"); - } - - int gprIndex = 0; - - for (int attachment = 0; attachment < 8; attachment++) - { - string output = GlslDecl.FragmentOutputName + attachment; - - OmapTarget target = _header.OmapTargets[attachment]; - - for (int component = 0; component < 4; component++) - { - if (target.ComponentEnabled(component)) - { - _sb.AppendLine(IdentationStr + output + "[" + component + "] = " + GlslDecl.GetGprName(gprIndex) + ";"); - - gprIndex++; - } - } - } - } - - _sb.AppendLine("}"); - } - - private void PrintProgram(ShaderIrBlock[] blocks, string name) - { - const string ident1 = IdentationStr; - const string ident2 = ident1 + IdentationStr; - const string ident3 = ident2 + IdentationStr; - const string ident4 = ident3 + IdentationStr; - - _sb.AppendLine(ident1 + "pc = " + GetBlockPosition(blocks[0]) + ";"); - _sb.AppendLine(ident1 + "do {"); - _sb.AppendLine(ident2 + "switch (pc) {"); - - foreach (ShaderIrBlock block in blocks) - { - string functionName = block.Position.ToString("x8"); - - _sb.AppendLine(ident3 + "case 0x" + functionName + ": pc = " + name + "_" + functionName + "(); break;"); - } - - _sb.AppendLine(ident3 + "default:"); - _sb.AppendLine(ident4 + "pc = 0;"); - _sb.AppendLine(ident4 + "break;"); - - _sb.AppendLine(ident2 + "}"); - _sb.AppendLine(ident1 + "} while (pc != 0);"); - } - - private void PrintAttrToOutput(string identation = IdentationStr) - { - foreach (KeyValuePair kv in _decl.OutAttributes) - { - if (!_decl.Attributes.TryGetValue(kv.Key, out ShaderDeclInfo attr)) - { - continue; - } - - ShaderDeclInfo declInfo = kv.Value; - - string name = attr.Name; - - if (_decl.ShaderType == GalShaderType.Geometry) - { - name += "[0]"; - } - - _sb.AppendLine(identation + declInfo.Name + " = " + name + ";"); - } - - if (_decl.ShaderType == GalShaderType.Vertex) - { - _sb.AppendLine(identation + "gl_Position.xy *= " + GlslDecl.FlipUniformName + ";"); - } - - if (_decl.ShaderType != GalShaderType.Fragment) - { - _sb.AppendLine(identation + GlslDecl.PositionOutAttrName + " = gl_Position;"); - _sb.AppendLine(identation + GlslDecl.PositionOutAttrName + ".w = 1;"); - } - } - - private void PrintBlockScope(ShaderIrBlock[] blocks, string name) - { - foreach (ShaderIrBlock block in blocks) - { - _sb.AppendLine("uint " + name + "_" + block.Position.ToString("x8") + "() {"); - - PrintNodes(block, block.GetNodes()); - - _sb.AppendLine("}" + Environment.NewLine); - } - } - - private void PrintNodes(ShaderIrBlock block, ShaderIrNode[] nodes) - { - foreach (ShaderIrNode node in nodes) - { - PrintNode(block, node, IdentationStr); - } - - if (nodes.Length == 0) - { - _sb.AppendLine(IdentationStr + "return 0u;"); - - return; - } - - ShaderIrNode last = nodes[nodes.Length - 1]; - - bool unconditionalFlowChange = false; - - if (last is ShaderIrOp op) - { - switch (op.Inst) - { - case ShaderIrInst.Bra: - case ShaderIrInst.Exit: - case ShaderIrInst.Sync: - unconditionalFlowChange = true; - break; - } - } - - if (!unconditionalFlowChange) - { - if (block.Next != null) - { - _sb.AppendLine(IdentationStr + "return " + GetBlockPosition(block.Next) + ";"); - } - else - { - _sb.AppendLine(IdentationStr + "return 0u;"); - } - } - } - - private void PrintNode(ShaderIrBlock block, ShaderIrNode node, string identation) - { - if (node is ShaderIrCond cond) - { - string ifExpr = GetSrcExpr(cond.Pred, true); - - if (cond.Not) - { - ifExpr = "!(" + ifExpr + ")"; - } - - _sb.AppendLine(identation + "if (" + ifExpr + ") {"); - - PrintNode(block, cond.Child, identation + IdentationStr); - - _sb.AppendLine(identation + "}"); - } - else if (node is ShaderIrAsg asg) - { - if (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) - { - switch (op.Inst) - { - case ShaderIrInst.Bra: - { - _sb.AppendLine(identation + "return " + GetBlockPosition(block.Branch) + ";"); - - break; - } - - case ShaderIrInst.Emit: - { - PrintAttrToOutput(identation); - - _sb.AppendLine(identation + "EmitVertex();"); - - break; - } - - case ShaderIrInst.Ssy: - { - string stackIndex = GlslDecl.SsyStackName + "[" + GlslDecl.SsyCursorName + "]"; - - int targetPosition = (op.OperandA as ShaderIrOperImm).Value; - - string target = "0x" + targetPosition.ToString("x8") + "u"; - - _sb.AppendLine(identation + stackIndex + " = " + target + ";"); - - _sb.AppendLine(identation + GlslDecl.SsyCursorName + "++;"); - - break; - } - - case ShaderIrInst.Sync: - { - _sb.AppendLine(identation + GlslDecl.SsyCursorName + "--;"); - - string target = GlslDecl.SsyStackName + "[" + GlslDecl.SsyCursorName + "]"; - - _sb.AppendLine(identation + "return " + target + ";"); - - break; - } - - default: - _sb.AppendLine(identation + GetSrcExpr(op, true) + ";"); - - break; - } - } - else if (node is ShaderIrCmnt cmnt) - { - _sb.AppendLine(identation + "// " + cmnt.Comment); - } - else - { - throw new InvalidOperationException(); - } - } - - 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.Ipa: - case ShaderIrInst.Texq: - case ShaderIrInst.Texs: - case ShaderIrInst.Tld4: - case ShaderIrInst.Txlf: - return false; - } - - return true; - } - - private string GetName(ShaderIrOperCbuf cbuf) - { - if (!_decl.Uniforms.TryGetValue(cbuf.Index, out ShaderDeclInfo declInfo)) - { - throw new InvalidOperationException(); - } - - if (cbuf.Offs != null) - { - string offset = "floatBitsToInt(" + GetSrcExpr(cbuf.Offs) + ")"; - - string index = "(" + cbuf.Pos * 4 + " + " + offset + ")"; - - return $"{declInfo.Name}_data[{index} / 16][({index} / 4) % 4]"; - } - else - { - return $"{declInfo.Name}_data[{cbuf.Pos / 4}][{cbuf.Pos % 4}]"; - } - } - - private string GetOutAbufName(ShaderIrOperAbuf abuf) - { - if (_decl.ShaderType == GalShaderType.Geometry) - { - switch (abuf.Offs) - { - case GlslDecl.LayerAttr: return "gl_Layer"; - } - } - - return GetAttrTempName(abuf); - } - - private string GetName(ShaderIrOperAbuf abuf) - { - //Handle special scalar read-only attributes here. - if (_decl.ShaderType == GalShaderType.Vertex) - { - switch (abuf.Offs) - { - case GlslDecl.VertexIdAttr: return "gl_VertexID"; - case GlslDecl.InstanceIdAttr: return GlslDecl.InstanceUniformName; - } - } - else if (_decl.ShaderType == GalShaderType.TessEvaluation) - { - switch (abuf.Offs) - { - case GlslDecl.TessCoordAttrX: return "gl_TessCoord.x"; - case GlslDecl.TessCoordAttrY: return "gl_TessCoord.y"; - case GlslDecl.TessCoordAttrZ: return "gl_TessCoord.z"; - } - } - else if (_decl.ShaderType == GalShaderType.Fragment) - { - switch (abuf.Offs) - { - case GlslDecl.PointCoordAttrX: return "gl_PointCoord.x"; - case GlslDecl.PointCoordAttrY: return "gl_PointCoord.y"; - case GlslDecl.FaceAttr: return "(gl_FrontFacing ? -1 : 0)"; - } - } - - return GetAttrTempName(abuf); - } - - private string GetAttrTempName(ShaderIrOperAbuf abuf) - { - int index = abuf.Offs >> 4; - int elem = (abuf.Offs >> 2) & 3; - - string swizzle = "." + GetAttrSwizzle(elem); - - if (!_decl.Attributes.TryGetValue(index, out ShaderDeclInfo declInfo)) - { - //Handle special vec4 attributes here - //(for example, index 7 is always gl_Position). - if (index == GlslDecl.GlPositionVec4Index) - { - string name = - _decl.ShaderType != GalShaderType.Vertex && - _decl.ShaderType != GalShaderType.Geometry ? GlslDecl.PositionOutAttrName : "gl_Position"; - - return name + swizzle; - } - else if (abuf.Offs == GlslDecl.PointSizeAttr) - { - return "gl_PointSize"; - } - } - - if (declInfo.Index >= 32) - { - throw new InvalidOperationException($"Shader attribute offset {abuf.Offs} is invalid."); - } - - if (_decl.ShaderType == GalShaderType.Geometry) - { - string vertex = "floatBitsToInt(" + GetSrcExpr(abuf.Vertex) + ")"; - - return declInfo.Name + "[" + vertex + "]" + swizzle; - } - else - { - return declInfo.Name + swizzle; - } - } - - private string GetName(ShaderIrOperGpr gpr) - { - if (gpr.IsConst) - { - return "0"; - } - - if (gpr.RegisterSize == ShaderRegisterSize.Single) - { - return GetNameWithSwizzle(_decl.Gprs, gpr.Index); - } - else if (gpr.RegisterSize == ShaderRegisterSize.Half) - { - return GetNameWithSwizzle(_decl.GprsHalf, (gpr.Index << 1) | gpr.HalfPart); - } - else /* if (Gpr.RegisterSize == ShaderRegisterSize.Double) */ - { - throw new NotImplementedException("Double types are not supported."); - } - } - - 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 GetIntConst(imm.Value); - } - } - - private string GetValue(ShaderIrOperImmf immf) - { - return GetFloatConst(immf.Value); - } - - private string GetName(ShaderIrOperPred pred) - { - return pred.IsConst ? "true" : GetNameWithSwizzle(_decl.Preds, pred.Index); - } - - private string GetNameWithSwizzle(IReadOnlyDictionary dict, int index) - { - int vecIndex = index & ~3; - - 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 GetAbsExpr(ShaderIrOp op) => GetUnaryCall(op, "abs"); - - private string GetAddExpr(ShaderIrOp op) => GetBinaryExpr(op, "+"); - - 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 GetBorExpr(ShaderIrOp op) => GetBinaryExpr(op, "||"); - - private string GetBxorExpr(ShaderIrOp op) => GetBinaryExpr(op, "^^"); - - private string GetCeilExpr(ShaderIrOp op) => GetUnaryCall(op, "ceil"); - - private string GetClampsExpr(ShaderIrOp op) - { - return "clamp(" + GetOperExpr(op, op.OperandA) + ", " + - GetOperExpr(op, op.OperandB) + ", " + - GetOperExpr(op, op.OperandC) + ")"; - } - - private string GetClampuExpr(ShaderIrOp op) - { - return "int(clamp(uint(" + GetOperExpr(op, op.OperandA) + "), " + - "uint(" + GetOperExpr(op, op.OperandB) + "), " + - "uint(" + GetOperExpr(op, op.OperandC) + ")))"; - } - - private string GetCeqExpr(ShaderIrOp op) => GetBinaryExpr(op, "=="); - - private string GetCequExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "=="); - - private string GetCgeExpr(ShaderIrOp op) => GetBinaryExpr(op, ">="); - - private string GetCgeuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, ">="); - - private string GetCgtExpr(ShaderIrOp op) => GetBinaryExpr(op, ">"); - - private string GetCgtuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, ">"); - - private string GetCleExpr(ShaderIrOp op) => GetBinaryExpr(op, "<="); - - private string GetCleuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "<="); - - private string GetCltExpr(ShaderIrOp op) => GetBinaryExpr(op, "<"); - - private string GetCltuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "<"); - - private string GetCnanExpr(ShaderIrOp op) => GetUnaryCall(op, "isnan"); - - private string GetCneExpr(ShaderIrOp op) => GetBinaryExpr(op, "!="); - - private string GetCutExpr(ShaderIrOp op) => "EndPrimitive()"; - - private string GetCneuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "!="); - - private string GetCnumExpr(ShaderIrOp op) => GetUnaryCall(op, "!isnan"); - - private string GetExitExpr(ShaderIrOp op) => "return 0u"; - - 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 GetFclampExpr(ShaderIrOp op) => GetTernaryCall(op, "clamp"); - - private string GetFlg2Expr(ShaderIrOp op) => GetUnaryCall(op, "log2"); - - private string GetFloorExpr(ShaderIrOp op) => GetUnaryCall(op, "floor"); - - private string GetFrcpExpr(ShaderIrOp op) => GetUnaryExpr(op, "1 / "); - - private string GetFrsqExpr(ShaderIrOp op) => GetUnaryCall(op, "inversesqrt"); - - private string GetFsinExpr(ShaderIrOp op) => GetUnaryCall(op, "sin"); - - private string GetFsqrtExpr(ShaderIrOp op) => GetUnaryCall(op, "sqrt"); - - private string GetFtosExpr(ShaderIrOp op) - { - return "int(" + GetOperExpr(op, op.OperandA) + ")"; - } - - private string GetFtouExpr(ShaderIrOp op) - { - return "int(uint(" + GetOperExpr(op, op.OperandA) + "))"; - } - - private string GetIpaExpr(ShaderIrOp op) - { - ShaderIrMetaIpa meta = (ShaderIrMetaIpa)op.MetaData; - - ShaderIrOperAbuf abuf = (ShaderIrOperAbuf)op.OperandA; - - if (meta.Mode == ShaderIpaMode.Pass) - { - int index = abuf.Offs >> 4; - int elem = (abuf.Offs >> 2) & 3; - - if (_decl.ShaderType == GalShaderType.Fragment && index == GlslDecl.GlPositionVec4Index) - { - switch (elem) - { - case 0: return "gl_FragCoord.x"; - case 1: return "gl_FragCoord.y"; - case 2: return "gl_FragCoord.z"; - case 3: return "1"; - } - } - } - - return GetSrcExpr(op.OperandA); - } - - private string GetKilExpr(ShaderIrOp op) => "discard"; - - private string GetLslExpr(ShaderIrOp op) => GetBinaryExpr(op, "<<"); - private string GetLsrExpr(ShaderIrOp op) - { - return "int(uint(" + GetOperExpr(op, op.OperandA) + ") >> " + - GetOperExpr(op, op.OperandB) + ")"; - } - - private string GetMaxExpr(ShaderIrOp op) => GetBinaryCall(op, "max"); - private string GetMinExpr(ShaderIrOp op) => GetBinaryCall(op, "min"); - - private string GetMulExpr(ShaderIrOp op) => GetBinaryExpr(op, "*"); - - private string GetNegExpr(ShaderIrOp op) => GetUnaryExpr(op, "-"); - - 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 GetSubExpr(ShaderIrOp op) => GetBinaryExpr(op, "-"); - - private string GetTexbExpr(ShaderIrOp op) - { - ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData; - - if (!_decl.CbTextures.TryGetValue(op, out ShaderDeclInfo declInfo)) - { - throw new InvalidOperationException(); - } - - string coords = GetTexSamplerCoords(op); - - string ch = "rgba".Substring(meta.Elem, 1); - - return GetTextureOperation(op, declInfo.Name, coords, ch); - } - - private string GetTexqExpr(ShaderIrOp op) - { - ShaderIrMetaTexq meta = (ShaderIrMetaTexq)op.MetaData; - - string ch = "xyzw".Substring(meta.Elem, 1); - - if (meta.Info == ShaderTexqInfo.Dimension) - { - string sampler = GetTexSamplerName(op); - - string lod = GetOperExpr(op, op.OperandA); //??? - - return "textureSize(" + sampler + ", " + lod + ")." + ch; - } - else - { - throw new NotImplementedException(meta.Info.ToString()); - } - } - - private string GetTexsExpr(ShaderIrOp op) - { - ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData; - - string sampler = GetTexSamplerName(op); - - string coords = GetTexSamplerCoords(op); - - string ch = "rgba".Substring(meta.Elem, 1); - - 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); - - 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"); - - 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 GetBinaryCall(ShaderIrOp op, string funcName) - { - return funcName + "(" + GetOperExpr(op, op.OperandA) + ", " + - GetOperExpr(op, op.OperandB) + ")"; - } - - private string GetTernaryCall(ShaderIrOp op, string funcName) - { - return funcName + "(" + GetOperExpr(op, op.OperandA) + ", " + - GetOperExpr(op, op.OperandB) + ", " + - GetOperExpr(op, op.OperandC) + ")"; - } - - 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 GetBinaryExprWithNaN(ShaderIrOp op, string opr) - { - string a = GetOperExpr(op, op.OperandA); - string b = GetOperExpr(op, op.OperandB); - - string nanCheck = - " || isnan(" + a + ")" + - " || isnan(" + b + ")"; - - return a + " " + opr + " " + b + nanCheck; - } - - private string GetTernaryExpr(ShaderIrOp op, string opr1, string opr2) - { - return GetOperExpr(op, op.OperandA) + " " + opr1 + " " + - GetOperExpr(op, op.OperandB) + " " + opr2 + " " + - GetOperExpr(op, op.OperandC); - } - - 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) - { - 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) - { - 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) - { - 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(); - } - - switch (src) - { - case ShaderIrOperGpr gpr: - { - //When the Gpr is ZR, just return the 0 value directly, - //since the float encoding for 0 is 0. - if (gpr.IsConst) - { - return "0"; - } - break; - } - } - - switch (dstType) - { - case OperType.F32: expr = "intBitsToFloat(" + expr + ")"; break; - case OperType.I32: expr = "floatBitsToInt(" + expr + ")"; break; - } - } - - return expr; - } - - private static string GetIntConst(int value) - { - string expr = value.ToString(CultureInfo.InvariantCulture); - - return value < 0 ? "(" + expr + ")" : expr; - } - - private static string GetFloatConst(float value) - { - string expr = value.ToString(CultureInfo.InvariantCulture); - - return value < 0 ? "(" + expr + ")" : expr; - } - - private static OperType GetDstNodeType(ShaderIrNode node) - { - //Special case instructions with the result type different - //from the input types (like integer <-> float conversion) here. - if (node is ShaderIrOp op) - { - switch (op.Inst) - { - case ShaderIrInst.Stof: - case ShaderIrInst.Txlf: - case ShaderIrInst.Utof: - return OperType.F32; - - case ShaderIrInst.Ftos: - case ShaderIrInst.Ftou: - return OperType.I32; - } - } - - return GetSrcNodeType(node); - } - - private static OperType GetSrcNodeType(ShaderIrNode node) - { - switch (node) - { - case ShaderIrOperAbuf abuf: - return abuf.Offs == GlslDecl.LayerAttr || - abuf.Offs == GlslDecl.InstanceIdAttr || - abuf.Offs == GlslDecl.VertexIdAttr || - abuf.Offs == GlslDecl.FaceAttr - ? 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)); - } - - private static string GetBlockPosition(ShaderIrBlock block) - { - if (block != null) - { - return "0x" + block.Position.ToString("x8") + "u"; - } - else - { - return "0u"; - } - } - } -} diff --git a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs deleted file mode 100644 index be8555d57..000000000 --- a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - public struct GlslProgram - { - public string Code { get; private set; } - - public IEnumerable Textures { get; private set; } - public IEnumerable Uniforms { get; private set; } - - public GlslProgram( - string code, - IEnumerable textures, - IEnumerable uniforms) - { - Code = code; - Textures = textures; - Uniforms = uniforms; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs deleted file mode 100644 index f935be74f..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs +++ /dev/null @@ -1,1299 +0,0 @@ -using System; - -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - private enum HalfOutputType - { - PackedFp16, - Fp32, - MergeH0, - MergeH1 - } - - public static void Bfe_C(ShaderIrBlock block, long opCode, int position) - { - EmitBfe(block, opCode, ShaderOper.Cr); - } - - public static void Bfe_I(ShaderIrBlock block, long opCode, int position) - { - EmitBfe(block, opCode, ShaderOper.Imm); - } - - public static void Bfe_R(ShaderIrBlock block, long opCode, int position) - { - EmitBfe(block, opCode, ShaderOper.Rr); - } - - public static void Fadd_C(ShaderIrBlock block, long opCode, int position) - { - EmitFadd(block, opCode, ShaderOper.Cr); - } - - public static void Fadd_I(ShaderIrBlock block, long opCode, int position) - { - EmitFadd(block, opCode, ShaderOper.Immf); - } - - public static void Fadd_I32(ShaderIrBlock block, long opCode, int position) - { - ShaderIrNode operA = opCode.Gpr8(); - ShaderIrNode operB = opCode.Immf32_20(); - - bool negB = opCode.Read(53); - bool absA = opCode.Read(54); - bool negA = opCode.Read(56); - bool absB = opCode.Read(57); - - operA = GetAluFabsFneg(operA, absA, negA); - operB = GetAluFabsFneg(operB, absB, negB); - - ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Fadd, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - public static void Fadd_R(ShaderIrBlock block, long opCode, int position) - { - EmitFadd(block, opCode, ShaderOper.Rr); - } - - public static void Ffma_CR(ShaderIrBlock block, long opCode, int position) - { - EmitFfma(block, opCode, ShaderOper.Cr); - } - - public static void Ffma_I(ShaderIrBlock block, long opCode, int position) - { - EmitFfma(block, opCode, ShaderOper.Immf); - } - - public static void Ffma_RC(ShaderIrBlock block, long opCode, int position) - { - EmitFfma(block, opCode, ShaderOper.Rc); - } - - public static void Ffma_RR(ShaderIrBlock block, long opCode, int position) - { - EmitFfma(block, opCode, ShaderOper.Rr); - } - - public static void Fmnmx_C(ShaderIrBlock block, long opCode, int position) - { - EmitFmnmx(block, opCode, ShaderOper.Cr); - } - - public static void Fmnmx_I(ShaderIrBlock block, long opCode, int position) - { - EmitFmnmx(block, opCode, ShaderOper.Immf); - } - - public static void Fmnmx_R(ShaderIrBlock block, long opCode, int position) - { - EmitFmnmx(block, opCode, ShaderOper.Rr); - } - - public static void Fmul_I32(ShaderIrBlock block, long opCode, int position) - { - ShaderIrNode operA = opCode.Gpr8(); - ShaderIrNode operB = opCode.Immf32_20(); - - ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Fmul, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - public static void Fmul_C(ShaderIrBlock block, long opCode, int position) - { - EmitFmul(block, opCode, ShaderOper.Cr); - } - - public static void Fmul_I(ShaderIrBlock block, long opCode, int position) - { - EmitFmul(block, opCode, ShaderOper.Immf); - } - - public static void Fmul_R(ShaderIrBlock block, long opCode, int position) - { - EmitFmul(block, opCode, ShaderOper.Rr); - } - - public static void Fset_C(ShaderIrBlock block, long opCode, int position) - { - EmitFset(block, opCode, ShaderOper.Cr); - } - - public static void Fset_I(ShaderIrBlock block, long opCode, int position) - { - EmitFset(block, opCode, ShaderOper.Immf); - } - - public static void Fset_R(ShaderIrBlock block, long opCode, int position) - { - EmitFset(block, opCode, ShaderOper.Rr); - } - - public static void Fsetp_C(ShaderIrBlock block, long opCode, int position) - { - EmitFsetp(block, opCode, ShaderOper.Cr); - } - - public static void Fsetp_I(ShaderIrBlock block, long opCode, int position) - { - EmitFsetp(block, opCode, ShaderOper.Immf); - } - - public static void Fsetp_R(ShaderIrBlock block, long opCode, int position) - { - EmitFsetp(block, opCode, ShaderOper.Rr); - } - - public static void Hadd2_R(ShaderIrBlock block, long opCode, int position) - { - EmitBinaryHalfOp(block, opCode, ShaderIrInst.Fadd); - } - - public static void Hmul2_R(ShaderIrBlock block, long opCode, int position) - { - EmitBinaryHalfOp(block, opCode, ShaderIrInst.Fmul); - } - - public static void Iadd_C(ShaderIrBlock block, long opCode, int position) - { - EmitIadd(block, opCode, ShaderOper.Cr); - } - - public static void Iadd_I(ShaderIrBlock block, long opCode, int position) - { - EmitIadd(block, opCode, ShaderOper.Imm); - } - - public static void Iadd_I32(ShaderIrBlock block, long opCode, int position) - { - ShaderIrNode operA = opCode.Gpr8(); - ShaderIrNode operB = opCode.Imm32_20(); - - bool negA = opCode.Read(56); - - operA = GetAluIneg(operA, negA); - - ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Add, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - public static void Iadd_R(ShaderIrBlock block, long opCode, int position) - { - EmitIadd(block, opCode, ShaderOper.Rr); - } - - public static void Iadd3_C(ShaderIrBlock block, long opCode, int position) - { - EmitIadd3(block, opCode, ShaderOper.Cr); - } - - public static void Iadd3_I(ShaderIrBlock block, long opCode, int position) - { - EmitIadd3(block, opCode, ShaderOper.Imm); - } - - public static void Iadd3_R(ShaderIrBlock block, long opCode, int position) - { - EmitIadd3(block, opCode, ShaderOper.Rr); - } - - public static void Imnmx_C(ShaderIrBlock block, long opCode, int position) - { - EmitImnmx(block, opCode, ShaderOper.Cr); - } - - public static void Imnmx_I(ShaderIrBlock block, long opCode, int position) - { - EmitImnmx(block, opCode, ShaderOper.Imm); - } - - public static void Imnmx_R(ShaderIrBlock block, long opCode, int position) - { - EmitImnmx(block, opCode, ShaderOper.Rr); - } - - public static void Ipa(ShaderIrBlock block, long opCode, int position) - { - ShaderIrNode operA = opCode.Abuf28(); - ShaderIrNode operB = opCode.Gpr20(); - - ShaderIpaMode mode = (ShaderIpaMode)(opCode.Read(54, 3)); - - ShaderIrMetaIpa meta = new ShaderIrMetaIpa(mode); - - ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Ipa, operA, operB, null, meta); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - public static void Iscadd_C(ShaderIrBlock block, long opCode, int position) - { - EmitIscadd(block, opCode, ShaderOper.Cr); - } - - public static void Iscadd_I(ShaderIrBlock block, long opCode, int position) - { - EmitIscadd(block, opCode, ShaderOper.Imm); - } - - public static void Iscadd_R(ShaderIrBlock block, long opCode, int position) - { - EmitIscadd(block, opCode, ShaderOper.Rr); - } - - public static void Iset_C(ShaderIrBlock block, long opCode, int position) - { - EmitIset(block, opCode, ShaderOper.Cr); - } - - public static void Iset_I(ShaderIrBlock block, long opCode, int position) - { - EmitIset(block, opCode, ShaderOper.Imm); - } - - public static void Iset_R(ShaderIrBlock block, long opCode, int position) - { - EmitIset(block, opCode, ShaderOper.Rr); - } - - public static void Isetp_C(ShaderIrBlock block, long opCode, int position) - { - EmitIsetp(block, opCode, ShaderOper.Cr); - } - - public static void Isetp_I(ShaderIrBlock block, long opCode, int position) - { - EmitIsetp(block, opCode, ShaderOper.Imm); - } - - public static void Isetp_R(ShaderIrBlock block, long opCode, int position) - { - EmitIsetp(block, opCode, ShaderOper.Rr); - } - - public static void Lop_I32(ShaderIrBlock block, long opCode, int position) - { - int subOp = opCode.Read(53, 3); - - bool invA = opCode.Read(55); - bool invB = opCode.Read(56); - - ShaderIrInst inst = 0; - - switch (subOp) - { - case 0: inst = ShaderIrInst.And; break; - case 1: inst = ShaderIrInst.Or; break; - case 2: inst = ShaderIrInst.Xor; break; - } - - ShaderIrNode operB = GetAluNot(opCode.Imm32_20(), invB); - - //SubOp == 3 is pass, used by the not instruction - //which just moves the inverted register value. - if (subOp < 3) - { - ShaderIrNode operA = GetAluNot(opCode.Gpr8(), invA); - - ShaderIrOp op = new ShaderIrOp(inst, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - else - { - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operB))); - } - } - - public static void Lop_C(ShaderIrBlock block, long opCode, int position) - { - EmitLop(block, opCode, ShaderOper.Cr); - } - - public static void Lop_I(ShaderIrBlock block, long opCode, int position) - { - EmitLop(block, opCode, ShaderOper.Imm); - } - - public static void Lop_R(ShaderIrBlock block, long opCode, int position) - { - EmitLop(block, opCode, ShaderOper.Rr); - } - - public static void Mufu(ShaderIrBlock block, long opCode, int position) - { - int subOp = opCode.Read(20, 0xf); - - bool absA = opCode.Read(46); - bool negA = opCode.Read(48); - - 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; - case 8: inst = ShaderIrInst.Fsqrt; break; - - default: throw new NotImplementedException(subOp.ToString()); - } - - ShaderIrNode operA = opCode.Gpr8(); - - ShaderIrOp op = new ShaderIrOp(inst, GetAluFabsFneg(operA, absA, negA)); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - public static void Psetp(ShaderIrBlock block, long opCode, int position) - { - bool negA = opCode.Read(15); - bool negB = opCode.Read(32); - bool negP = opCode.Read(42); - - ShaderIrInst lopInst = opCode.BLop24(); - - ShaderIrNode operA = opCode.Pred12(); - ShaderIrNode operB = opCode.Pred29(); - - if (negA) - { - operA = new ShaderIrOp(ShaderIrInst.Bnot, operA); - } - - if (negB) - { - operB = new ShaderIrOp(ShaderIrInst.Bnot, operB); - } - - ShaderIrOp op = new ShaderIrOp(lopInst, operA, operB); - - ShaderIrOperPred p0Node = opCode.Pred3(); - ShaderIrOperPred p1Node = opCode.Pred0(); - ShaderIrOperPred p2Node = opCode.Pred39(); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op))); - - lopInst = opCode.BLop45(); - - if (lopInst == ShaderIrInst.Band && p1Node.IsConst && p2Node.IsConst) - { - return; - } - - ShaderIrNode p2NNode = p2Node; - - if (negP) - { - p2NNode = new ShaderIrOp(ShaderIrInst.Bnot, p2NNode); - } - - op = new ShaderIrOp(ShaderIrInst.Bnot, p0Node); - - op = new ShaderIrOp(lopInst, op, p2NNode); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(p1Node, op))); - - op = new ShaderIrOp(lopInst, p0Node, p2NNode); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op))); - } - - public static void Rro_C(ShaderIrBlock block, long opCode, int position) - { - EmitRro(block, opCode, ShaderOper.Cr); - } - - public static void Rro_I(ShaderIrBlock block, long opCode, int position) - { - EmitRro(block, opCode, ShaderOper.Immf); - } - - public static void Rro_R(ShaderIrBlock block, long opCode, int position) - { - EmitRro(block, opCode, ShaderOper.Rr); - } - - public static void Shl_C(ShaderIrBlock block, long opCode, int position) - { - EmitAluBinary(block, opCode, ShaderOper.Cr, ShaderIrInst.Lsl); - } - - public static void Shl_I(ShaderIrBlock block, long opCode, int position) - { - EmitAluBinary(block, opCode, ShaderOper.Imm, ShaderIrInst.Lsl); - } - - public static void Shl_R(ShaderIrBlock block, long opCode, int position) - { - EmitAluBinary(block, opCode, ShaderOper.Rr, ShaderIrInst.Lsl); - } - - public static void Shr_C(ShaderIrBlock block, long opCode, int position) - { - EmitAluBinary(block, opCode, ShaderOper.Cr, GetShrInst(opCode)); - } - - public static void Shr_I(ShaderIrBlock block, long opCode, int position) - { - EmitAluBinary(block, opCode, ShaderOper.Imm, GetShrInst(opCode)); - } - - public static void Shr_R(ShaderIrBlock block, long opCode, int position) - { - EmitAluBinary(block, opCode, ShaderOper.Rr, GetShrInst(opCode)); - } - - private static ShaderIrInst GetShrInst(long opCode) - { - bool signed = opCode.Read(48); - - return signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr; - } - - public static void Vmad(ShaderIrBlock block, long opCode, int position) - { - ShaderIrNode operA = opCode.Gpr8(); - - ShaderIrNode operB; - - if (opCode.Read(50)) - { - operB = opCode.Gpr20(); - } - else - { - operB = opCode.Imm19_20(); - } - - ShaderIrOperGpr operC = opCode.Gpr39(); - - ShaderIrNode tmp = new ShaderIrOp(ShaderIrInst.Mul, operA, operB); - - ShaderIrNode final = new ShaderIrOp(ShaderIrInst.Add, tmp, operC); - - int shr = opCode.Read(51, 3); - - if (shr != 0) - { - int shift = (shr == 2) ? 15 : 7; - - final = new ShaderIrOp(ShaderIrInst.Lsr, final, new ShaderIrOperImm(shift)); - } - - block.AddNode(new ShaderIrCmnt("Stubbed. Instruction is reduced to a * b + c")); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), final))); - } - - public static void Xmad_CR(ShaderIrBlock block, long opCode, int position) - { - EmitXmad(block, opCode, ShaderOper.Cr); - } - - public static void Xmad_I(ShaderIrBlock block, long opCode, int position) - { - EmitXmad(block, opCode, ShaderOper.Imm); - } - - public static void Xmad_RC(ShaderIrBlock block, long opCode, int position) - { - EmitXmad(block, opCode, ShaderOper.Rc); - } - - public static void Xmad_RR(ShaderIrBlock block, long opCode, int position) - { - EmitXmad(block, opCode, ShaderOper.Rr); - } - - private static void EmitAluBinary( - ShaderIrBlock block, - long opCode, - ShaderOper oper, - ShaderIrInst inst) - { - ShaderIrNode operA = opCode.Gpr8(), operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - ShaderIrNode op = new ShaderIrOp(inst, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - private static void EmitBfe(ShaderIrBlock block, long opCode, ShaderOper oper) - { - //TODO: Handle the case where position + length - //is greater than the word size, in this case the sign bit - //needs to be replicated to fill the remaining space. - bool negB = opCode.Read(48); - bool negA = opCode.Read(49); - - ShaderIrNode operA = opCode.Gpr8(), operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - ShaderIrNode op; - - bool signed = opCode.Read(48); //? - - if (operB is ShaderIrOperImm posLen) - { - int position = (posLen.Value >> 0) & 0xff; - int length = (posLen.Value >> 8) & 0xff; - - int lSh = 32 - (position + length); - - ShaderIrInst rightShift = signed - ? ShaderIrInst.Asr - : ShaderIrInst.Lsr; - - op = new ShaderIrOp(ShaderIrInst.Lsl, operA, new ShaderIrOperImm(lSh)); - op = new ShaderIrOp(rightShift, op, new ShaderIrOperImm(lSh + position)); - } - else - { - ShaderIrOperImm shift = new ShaderIrOperImm(8); - ShaderIrOperImm mask = new ShaderIrOperImm(0xff); - - ShaderIrNode opPos, opLen; - - opPos = new ShaderIrOp(ShaderIrInst.And, operB, mask); - opLen = new ShaderIrOp(ShaderIrInst.Lsr, operB, shift); - opLen = new ShaderIrOp(ShaderIrInst.And, opLen, mask); - - op = new ShaderIrOp(ShaderIrInst.Lsr, operA, opPos); - - op = ExtendTo32(op, signed, opLen); - } - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - private static void EmitFadd(ShaderIrBlock block, long opCode, ShaderOper oper) - { - bool negB = opCode.Read(45); - bool absA = opCode.Read(46); - bool negA = opCode.Read(48); - bool absB = opCode.Read(49); - bool sat = opCode.Read(50); - - ShaderIrNode operA = opCode.Gpr8(), operB; - - operA = GetAluFabsFneg(operA, absA, negA); - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Immf: operB = opCode.Immf19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operB = GetAluFabsFneg(operB, absB, negB); - - ShaderIrNode op = new ShaderIrOp(ShaderIrInst.Fadd, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), GetAluFsat(op, sat)))); - } - - private static void EmitFmul(ShaderIrBlock block, long opCode, ShaderOper oper) - { - bool negB = opCode.Read(48); - bool sat = opCode.Read(50); - - ShaderIrNode operA = opCode.Gpr8(), operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Immf: operB = opCode.Immf19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operB = GetAluFneg(operB, negB); - - ShaderIrNode op = new ShaderIrOp(ShaderIrInst.Fmul, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), GetAluFsat(op, sat)))); - } - - private static void EmitFfma(ShaderIrBlock block, long opCode, ShaderOper oper) - { - bool negB = opCode.Read(48); - bool negC = opCode.Read(49); - bool sat = opCode.Read(50); - - ShaderIrNode operA = opCode.Gpr8(), operB, operC; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Immf: operB = opCode.Immf19_20(); break; - case ShaderOper.Rc: operB = opCode.Gpr39(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operB = GetAluFneg(operB, negB); - - if (oper == ShaderOper.Rc) - { - operC = GetAluFneg(opCode.Cbuf34(), negC); - } - else - { - operC = GetAluFneg(opCode.Gpr39(), negC); - } - - ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Ffma, operA, operB, operC); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), GetAluFsat(op, sat)))); - } - - private static void EmitIadd(ShaderIrBlock block, long opCode, ShaderOper oper) - { - ShaderIrNode operA = opCode.Gpr8(); - ShaderIrNode operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - bool negA = opCode.Read(49); - bool negB = opCode.Read(48); - - operA = GetAluIneg(operA, negA); - operB = GetAluIneg(operB, negB); - - ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Add, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - private static void EmitIadd3(ShaderIrBlock block, long opCode, ShaderOper oper) - { - int mode = opCode.Read(37, 3); - - bool neg1 = opCode.Read(51); - bool neg2 = opCode.Read(50); - bool neg3 = opCode.Read(49); - - int height1 = opCode.Read(35, 3); - int height2 = opCode.Read(33, 3); - int height3 = opCode.Read(31, 3); - - ShaderIrNode operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - ShaderIrNode ApplyHeight(ShaderIrNode src, int height) - { - if (oper != ShaderOper.Rr) - { - return src; - } - - switch (height) - { - case 0: return src; - case 1: return new ShaderIrOp(ShaderIrInst.And, src, new ShaderIrOperImm(0xffff)); - case 2: return new ShaderIrOp(ShaderIrInst.Lsr, src, new ShaderIrOperImm(16)); - - default: throw new InvalidOperationException(); - } - } - - ShaderIrNode src1 = GetAluIneg(ApplyHeight(opCode.Gpr8(), height1), neg1); - ShaderIrNode src2 = GetAluIneg(ApplyHeight(operB, height2), neg2); - ShaderIrNode src3 = GetAluIneg(ApplyHeight(opCode.Gpr39(), height3), neg3); - - ShaderIrOp sum = new ShaderIrOp(ShaderIrInst.Add, src1, src2); - - if (oper == ShaderOper.Rr) - { - switch (mode) - { - case 1: sum = new ShaderIrOp(ShaderIrInst.Lsr, sum, new ShaderIrOperImm(16)); break; - case 2: sum = new ShaderIrOp(ShaderIrInst.Lsl, sum, new ShaderIrOperImm(16)); break; - } - } - - //Note: Here there should be a "+ 1" when carry flag is set - //but since carry is mostly ignored by other instructions, it's excluded for now - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), new ShaderIrOp(ShaderIrInst.Add, sum, src3)))); - } - - private static void EmitIscadd(ShaderIrBlock block, long opCode, ShaderOper oper) - { - bool negB = opCode.Read(48); - bool negA = opCode.Read(49); - - ShaderIrNode operA = opCode.Gpr8(), operB; - - ShaderIrOperImm scale = opCode.Imm5_39(); - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operA = GetAluIneg(operA, negA); - operB = GetAluIneg(operB, negB); - - ShaderIrOp scaleOp = new ShaderIrOp(ShaderIrInst.Lsl, operA, scale); - ShaderIrOp addOp = new ShaderIrOp(ShaderIrInst.Add, operB, scaleOp); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), addOp))); - } - - private static void EmitFmnmx(ShaderIrBlock block, long opCode, ShaderOper oper) - { - EmitMnmx(block, opCode, true, oper); - } - - private static void EmitImnmx(ShaderIrBlock block, long opCode, ShaderOper oper) - { - EmitMnmx(block, opCode, false, oper); - } - - private static void EmitMnmx(ShaderIrBlock block, long opCode, bool isFloat, ShaderOper oper) - { - bool negB = opCode.Read(45); - bool absA = opCode.Read(46); - bool negA = opCode.Read(48); - bool absB = opCode.Read(49); - - ShaderIrNode operA = opCode.Gpr8(), operB; - - if (isFloat) - { - operA = GetAluFabsFneg(operA, absA, negA); - } - else - { - operA = GetAluIabsIneg(operA, absA, negA); - } - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Immf: operB = opCode.Immf19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - if (isFloat) - { - operB = GetAluFabsFneg(operB, absB, negB); - } - else - { - operB = GetAluIabsIneg(operB, absB, negB); - } - - ShaderIrOperPred pred = opCode.Pred39(); - - ShaderIrOp op; - - ShaderIrInst maxInst = isFloat ? ShaderIrInst.Fmax : ShaderIrInst.Max; - ShaderIrInst minInst = isFloat ? ShaderIrInst.Fmin : ShaderIrInst.Min; - - if (pred.IsConst) - { - bool isMax = opCode.Read(42); - - op = new ShaderIrOp(isMax - ? maxInst - : minInst, operA, operB); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - else - { - ShaderIrNode predN = opCode.Pred39N(); - - ShaderIrOp opMax = new ShaderIrOp(maxInst, operA, operB); - ShaderIrOp opMin = new ShaderIrOp(minInst, operA, operB); - - ShaderIrAsg asgMax = new ShaderIrAsg(opCode.Gpr0(), opMax); - ShaderIrAsg asgMin = new ShaderIrAsg(opCode.Gpr0(), opMin); - - block.AddNode(opCode.PredNode(new ShaderIrCond(predN, asgMax, not: true))); - block.AddNode(opCode.PredNode(new ShaderIrCond(predN, asgMin, not: false))); - } - } - - private static void EmitRro(ShaderIrBlock block, long opCode, ShaderOper oper) - { - //Note: this is a range reduction instruction and is supposed to - //be used with Mufu, here it just moves the value and ignores the operation. - bool negA = opCode.Read(45); - bool absA = opCode.Read(49); - - ShaderIrNode operA; - - switch (oper) - { - case ShaderOper.Cr: operA = opCode.Cbuf34(); break; - case ShaderOper.Immf: operA = opCode.Immf19_20(); break; - case ShaderOper.Rr: operA = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operA = GetAluFabsFneg(operA, absA, negA); - - block.AddNode(new ShaderIrCmnt("Stubbed.")); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operA))); - } - - private static void EmitFset(ShaderIrBlock block, long opCode, ShaderOper oper) - { - EmitSet(block, opCode, true, oper); - } - - private static void EmitIset(ShaderIrBlock block, long opCode, ShaderOper oper) - { - EmitSet(block, opCode, false, oper); - } - - private static void EmitSet(ShaderIrBlock block, long opCode, bool isFloat, ShaderOper oper) - { - bool negA = opCode.Read(43); - bool absB = opCode.Read(44); - bool negB = opCode.Read(53); - bool absA = opCode.Read(54); - - bool boolFloat = opCode.Read(isFloat ? 52 : 44); - - ShaderIrNode operA = opCode.Gpr8(), operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Immf: operB = opCode.Immf19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - ShaderIrInst cmpInst; - - if (isFloat) - { - operA = GetAluFabsFneg(operA, absA, negA); - operB = GetAluFabsFneg(operB, absB, negB); - - cmpInst = opCode.CmpF(); - } - else - { - cmpInst = opCode.Cmp(); - } - - ShaderIrOp op = new ShaderIrOp(cmpInst, operA, operB); - - ShaderIrInst lopInst = opCode.BLop45(); - - ShaderIrOperPred pNode = opCode.Pred39(); - - ShaderIrNode imm0, imm1; - - if (boolFloat) - { - imm0 = new ShaderIrOperImmf(0); - imm1 = new ShaderIrOperImmf(1); - } - else - { - imm0 = new ShaderIrOperImm(0); - imm1 = new ShaderIrOperImm(-1); - } - - ShaderIrNode asg0 = new ShaderIrAsg(opCode.Gpr0(), imm0); - ShaderIrNode asg1 = new ShaderIrAsg(opCode.Gpr0(), imm1); - - if (lopInst != ShaderIrInst.Band || !pNode.IsConst) - { - ShaderIrOp op2 = new ShaderIrOp(lopInst, op, pNode); - - asg0 = new ShaderIrCond(op2, asg0, not: true); - asg1 = new ShaderIrCond(op2, asg1, not: false); - } - else - { - asg0 = new ShaderIrCond(op, asg0, not: true); - asg1 = new ShaderIrCond(op, asg1, not: false); - } - - block.AddNode(opCode.PredNode(asg0)); - block.AddNode(opCode.PredNode(asg1)); - } - - private static void EmitFsetp(ShaderIrBlock block, long opCode, ShaderOper oper) - { - EmitSetp(block, opCode, true, oper); - } - - private static void EmitIsetp(ShaderIrBlock block, long opCode, ShaderOper oper) - { - EmitSetp(block, opCode, false, oper); - } - - private static void EmitSetp(ShaderIrBlock block, long opCode, bool isFloat, ShaderOper oper) - { - bool absA = opCode.Read(7); - bool negP = opCode.Read(42); - bool negA = opCode.Read(43); - bool absB = opCode.Read(44); - - ShaderIrNode operA = opCode.Gpr8(), operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Immf: operB = opCode.Immf19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - ShaderIrInst cmpInst; - - if (isFloat) - { - operA = GetAluFabsFneg(operA, absA, negA); - operB = GetAluFabs (operB, absB); - - cmpInst = opCode.CmpF(); - } - else - { - cmpInst = opCode.Cmp(); - } - - ShaderIrOp op = new ShaderIrOp(cmpInst, operA, operB); - - ShaderIrOperPred p0Node = opCode.Pred3(); - ShaderIrOperPred p1Node = opCode.Pred0(); - ShaderIrOperPred p2Node = opCode.Pred39(); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op))); - - ShaderIrInst lopInst = opCode.BLop45(); - - if (lopInst == ShaderIrInst.Band && p1Node.IsConst && p2Node.IsConst) - { - return; - } - - ShaderIrNode p2NNode = p2Node; - - if (negP) - { - p2NNode = new ShaderIrOp(ShaderIrInst.Bnot, p2NNode); - } - - op = new ShaderIrOp(ShaderIrInst.Bnot, p0Node); - - op = new ShaderIrOp(lopInst, op, p2NNode); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(p1Node, op))); - - op = new ShaderIrOp(lopInst, p0Node, p2NNode); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op))); - } - - private static void EmitBinaryHalfOp(ShaderIrBlock block, long opCode, ShaderIrInst inst) - { - bool absB = opCode.Read(30); - bool negB = opCode.Read(31); - bool sat = opCode.Read(32); - bool absA = opCode.Read(44); - - ShaderIrOperGpr[] vecA = opCode.GprHalfVec8(); - ShaderIrOperGpr[] vecB = opCode.GprHalfVec20(); - - HalfOutputType outputType = (HalfOutputType)opCode.Read(49, 3); - - int elems = outputType == HalfOutputType.PackedFp16 ? 2 : 1; - int first = outputType == HalfOutputType.MergeH1 ? 1 : 0; - - for (int index = first; index < elems; index++) - { - ShaderIrNode operA = GetAluFabs (vecA[index], absA); - ShaderIrNode operB = GetAluFabsFneg(vecB[index], absB, negB); - - ShaderIrNode op = new ShaderIrOp(inst, operA, operB); - - ShaderIrOperGpr dst = GetHalfDst(opCode, outputType, index); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(dst, GetAluFsat(op, sat)))); - } - } - - private static ShaderIrOperGpr GetHalfDst(long opCode, HalfOutputType outputType, int index) - { - switch (outputType) - { - case HalfOutputType.PackedFp16: return opCode.GprHalf0(index); - case HalfOutputType.Fp32: return opCode.Gpr0(); - case HalfOutputType.MergeH0: return opCode.GprHalf0(0); - case HalfOutputType.MergeH1: return opCode.GprHalf0(1); - } - - throw new ArgumentException(nameof(outputType)); - } - - private static void EmitLop(ShaderIrBlock block, long opCode, ShaderOper oper) - { - int subOp = opCode.Read(41, 3); - - bool invA = opCode.Read(39); - bool invB = opCode.Read(40); - - 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(opCode.Gpr8(), invA); - ShaderIrNode operB; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.Imm19_20(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operB = GetAluNot(operB, invB); - - ShaderIrNode op; - - if (subOp < 3) - { - op = new ShaderIrOp(inst, operA, operB); - } - else - { - op = operB; - } - - ShaderIrNode compare = new ShaderIrOp(ShaderIrInst.Cne, op, new ShaderIrOperImm(0)); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Pred48(), compare))); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - private enum XmadMode - { - Cfull = 0, - Clo = 1, - Chi = 2, - Csfu = 3, - Cbcc = 4 - } - - private static void EmitXmad(ShaderIrBlock block, long opCode, ShaderOper oper) - { - bool signedA = opCode.Read(48); - bool signedB = opCode.Read(49); - bool highB = opCode.Read(52); - bool highA = opCode.Read(53); - - int mode = opCode.Read(50, 7); - - ShaderIrNode operA = opCode.Gpr8(), operB, operC; - - switch (oper) - { - case ShaderOper.Cr: operB = opCode.Cbuf34(); break; - case ShaderOper.Imm: operB = opCode.ImmU16_20(); break; - case ShaderOper.Rc: operB = opCode.Gpr39(); break; - case ShaderOper.Rr: operB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - ShaderIrNode operB2 = operB; - - if (oper == ShaderOper.Imm) - { - int imm = ((ShaderIrOperImm)operB2).Value; - - if (!highB) - { - imm <<= 16; - } - - if (signedB) - { - imm >>= 16; - } - else - { - imm = (int)((uint)imm >> 16); - } - - operB2 = new ShaderIrOperImm(imm); - } - - ShaderIrOperImm imm16 = new ShaderIrOperImm(16); - - //If we are working with the lower 16-bits of the A/B operands, - //we need to shift the lower 16-bits to the top 16-bits. Later, - //they will be right shifted. For U16 types, this will be a logical - //right shift, and for S16 types, a arithmetic right shift. - if (!highA) - { - operA = new ShaderIrOp(ShaderIrInst.Lsl, operA, imm16); - } - - if (!highB && oper != ShaderOper.Imm) - { - operB2 = new ShaderIrOp(ShaderIrInst.Lsl, operB2, imm16); - } - - ShaderIrInst shiftA = signedA ? ShaderIrInst.Asr : ShaderIrInst.Lsr; - ShaderIrInst shiftB = signedB ? ShaderIrInst.Asr : ShaderIrInst.Lsr; - - operA = new ShaderIrOp(shiftA, operA, imm16); - - if (oper != ShaderOper.Imm) - { - operB2 = new ShaderIrOp(shiftB, operB2, imm16); - } - - bool productShiftLeft = false; - bool merge = false; - - if (oper == ShaderOper.Rc) - { - operC = opCode.Cbuf34(); - } - else - { - operC = opCode.Gpr39(); - - productShiftLeft = opCode.Read(36); - merge = opCode.Read(37); - } - - ShaderIrOp mulOp = new ShaderIrOp(ShaderIrInst.Mul, operA, operB2); - - if (productShiftLeft) - { - mulOp = new ShaderIrOp(ShaderIrInst.Lsl, mulOp, imm16); - } - - switch ((XmadMode)mode) - { - case XmadMode.Clo: operC = ExtendTo32(operC, signed: false, size: 16); break; - - case XmadMode.Chi: operC = new ShaderIrOp(ShaderIrInst.Lsr, operC, imm16); break; - - case XmadMode.Cbcc: - { - ShaderIrOp operBLsh16 = new ShaderIrOp(ShaderIrInst.Lsl, operB, imm16); - - operC = new ShaderIrOp(ShaderIrInst.Add, operC, operBLsh16); - - break; - } - - case XmadMode.Csfu: - { - ShaderIrOperImm imm31 = new ShaderIrOperImm(31); - - ShaderIrOp signAdjustA = new ShaderIrOp(ShaderIrInst.Lsr, operA, imm31); - ShaderIrOp signAdjustB = new ShaderIrOp(ShaderIrInst.Lsr, operB2, imm31); - - signAdjustA = new ShaderIrOp(ShaderIrInst.Lsl, signAdjustA, imm16); - signAdjustB = new ShaderIrOp(ShaderIrInst.Lsl, signAdjustB, imm16); - - ShaderIrOp signAdjust = new ShaderIrOp(ShaderIrInst.Add, signAdjustA, signAdjustB); - - operC = new ShaderIrOp(ShaderIrInst.Sub, operC, signAdjust); - - break; - } - } - - ShaderIrOp addOp = new ShaderIrOp(ShaderIrInst.Add, mulOp, operC); - - if (merge) - { - ShaderIrOperImm imm16Mask = new ShaderIrOperImm(0xffff); - - addOp = new ShaderIrOp(ShaderIrInst.And, addOp, imm16Mask); - operB = new ShaderIrOp(ShaderIrInst.Lsl, operB, imm16); - addOp = new ShaderIrOp(ShaderIrInst.Or, addOp, operB); - } - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), addOp))); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs deleted file mode 100644 index fc9926934..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - public static void Bra(ShaderIrBlock block, long opCode, int position) - { - if ((opCode & 0x20) != 0) - { - //This reads the target offset from the constant buffer. - //Almost impossible to support with GLSL. - throw new NotImplementedException(); - } - - ShaderIrOperImm imm = new ShaderIrOperImm(position + opCode.Branch()); - - block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Bra, imm))); - } - - public static void Exit(ShaderIrBlock block, long opCode, int position) - { - int cCode = (int)opCode & 0x1f; - - //TODO: Figure out what the other condition codes mean... - if (cCode == 0xf) - { - block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Exit))); - } - } - - public static void Kil(ShaderIrBlock block, long opCode, int position) - { - block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Kil))); - } - - public static void Ssy(ShaderIrBlock block, long opCode, int position) - { - if ((opCode & 0x20) != 0) - { - //This reads the target offset from the constant buffer. - //Almost impossible to support with GLSL. - throw new NotImplementedException(); - } - - ShaderIrOperImm imm = new ShaderIrOperImm(position + opCode.Branch()); - - block.AddNode(new ShaderIrOp(ShaderIrInst.Ssy, imm)); - } - - public static void Sync(ShaderIrBlock block, long opCode, int position) - { - //TODO: Implement Sync condition codes - block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Sync))); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs deleted file mode 100644 index cc385aa4e..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - delegate void ShaderDecodeFunc(ShaderIrBlock block, long opCode, int position); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs deleted file mode 100644 index 9a84e6129..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - static class ShaderDecodeHelper - { - private static readonly ShaderIrOperImmf ImmfZero = new ShaderIrOperImmf(0); - private static readonly ShaderIrOperImmf ImmfOne = new ShaderIrOperImmf(1); - - public static ShaderIrNode GetAluFabsFneg(ShaderIrNode node, bool abs, bool neg) - { - return GetAluFneg(GetAluFabs(node, abs), neg); - } - - public static ShaderIrNode GetAluFabs(ShaderIrNode node, bool abs) - { - return abs ? new ShaderIrOp(ShaderIrInst.Fabs, node) : node; - } - - public static ShaderIrNode GetAluFneg(ShaderIrNode node, bool neg) - { - return neg ? new ShaderIrOp(ShaderIrInst.Fneg, node) : node; - } - - public static ShaderIrNode GetAluFsat(ShaderIrNode node, bool sat) - { - return sat ? new ShaderIrOp(ShaderIrInst.Fclamp, node, ImmfZero, ImmfOne) : node; - } - - public static ShaderIrNode GetAluIabsIneg(ShaderIrNode node, bool abs, bool neg) - { - return GetAluIneg(GetAluIabs(node, abs), neg); - } - - public static ShaderIrNode GetAluIabs(ShaderIrNode node, bool abs) - { - return abs ? new ShaderIrOp(ShaderIrInst.Abs, node) : node; - } - - public static ShaderIrNode GetAluIneg(ShaderIrNode node, bool neg) - { - return neg ? new ShaderIrOp(ShaderIrInst.Neg, node) : node; - } - - public static ShaderIrNode GetAluNot(ShaderIrNode node, bool not) - { - return not ? new ShaderIrOp(ShaderIrInst.Not, node) : node; - } - - public static ShaderIrNode ExtendTo32(ShaderIrNode node, bool signed, int size) - { - int shift = 32 - size; - - ShaderIrInst rightShift = signed - ? ShaderIrInst.Asr - : ShaderIrInst.Lsr; - - node = new ShaderIrOp(ShaderIrInst.Lsl, node, new ShaderIrOperImm(shift)); - node = new ShaderIrOp(rightShift, node, new ShaderIrOperImm(shift)); - - return node; - } - - public static ShaderIrNode ExtendTo32(ShaderIrNode node, bool signed, ShaderIrNode size) - { - ShaderIrOperImm wordSize = new ShaderIrOperImm(32); - - ShaderIrOp shift = new ShaderIrOp(ShaderIrInst.Sub, wordSize, size); - - ShaderIrInst rightShift = signed - ? ShaderIrInst.Asr - : ShaderIrInst.Lsr; - - node = new ShaderIrOp(ShaderIrInst.Lsl, node, shift); - node = new ShaderIrOp(rightShift, node, shift); - - return node; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs deleted file mode 100644 index 7ce126b0c..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs +++ /dev/null @@ -1,878 +0,0 @@ -using Ryujinx.Graphics.Texture; -using System; - -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - // ReSharper disable InconsistentNaming - private const int ____ = 0x0; - private const int R___ = 0x1; - private const int _G__ = 0x2; - private const int RG__ = 0x3; - private const int __B_ = 0x4; - private const int RGB_ = 0x7; - private const int ___A = 0x8; - private const int R__A = 0x9; - private const int _G_A = 0xa; - private const int RG_A = 0xb; - private const int __BA = 0xc; - private const int R_BA = 0xd; - private const int _GBA = 0xe; - private const int RGBA = 0xf; - // ReSharper restore InconsistentNaming - - private static int[,] _maskLut = new int[,] - { - { ____, ____, ____, ____, ____, ____, ____, ____ }, - { R___, _G__, __B_, ___A, RG__, R__A, _G_A, __BA }, - { R___, _G__, __B_, ___A, RG__, ____, ____, ____ }, - { 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(); - - //Used by GS - ShaderIrOperGpr vertex = opCode.Gpr39(); - - int index = 0; - - foreach (ShaderIrNode operA in opers) - { - ShaderIrOperGpr operD = opCode.Gpr0(); - - operD.Index += index++; - - block.AddNode(opCode.PredNode(new ShaderIrAsg(operD, operA))); - } - } - - public static void Ld_C(ShaderIrBlock block, long opCode, int position) - { - int cbufPos = opCode.Read(22, 0x3fff); - int cbufIndex = opCode.Read(36, 0x1f); - int type = opCode.Read(48, 7); - - if (type > 5) - { - throw new InvalidOperationException(); - } - - ShaderIrOperGpr temp = ShaderIrOperGpr.MakeTemporary(); - - block.AddNode(new ShaderIrAsg(temp, opCode.Gpr8())); - - int count = type == 5 ? 2 : 1; - - for (int index = 0; index < count; index++) - { - ShaderIrOperCbuf operA = new ShaderIrOperCbuf(cbufIndex, cbufPos, temp); - - ShaderIrOperGpr operD = opCode.Gpr0(); - - operA.Pos += index; - operD.Index += index; - - if (!operD.IsValidRegister) - { - break; - } - - ShaderIrNode node = operA; - - if (type < 4) - { - //This is a 8 or 16 bits type. - bool signed = (type & 1) != 0; - - int size = 8 << (type >> 1); - - node = ExtendTo32(node, signed, size); - } - - block.AddNode(opCode.PredNode(new ShaderIrAsg(operD, node))); - } - } - - public static void St_A(ShaderIrBlock block, long opCode, int position) - { - ShaderIrNode[] opers = opCode.Abuf20(); - - int index = 0; - - foreach (ShaderIrNode operA in opers) - { - ShaderIrOperGpr operD = opCode.Gpr0(); - - operD.Index += index++; - - block.AddNode(opCode.PredNode(new ShaderIrAsg(operA, operD))); - } - } - - public static void Texq(ShaderIrBlock block, long opCode, int position) - { - ShaderIrNode operD = opCode.Gpr0(); - ShaderIrNode operA = opCode.Gpr8(); - - ShaderTexqInfo info = (ShaderTexqInfo)(opCode.Read(22, 0x1f)); - - ShaderIrMetaTexq meta0 = new ShaderIrMetaTexq(info, 0); - ShaderIrMetaTexq meta1 = new ShaderIrMetaTexq(info, 1); - - ShaderIrNode operC = opCode.Imm13_36(); - - ShaderIrOp op0 = new ShaderIrOp(ShaderIrInst.Texq, operA, null, operC, meta0); - ShaderIrOp op1 = new ShaderIrOp(ShaderIrInst.Texq, operA, null, operC, meta1); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(operD, op0))); - block.AddNode(opCode.PredNode(new ShaderIrAsg(operA, op1))); //Is this right? - } - - public static void Tex(ShaderIrBlock block, long opCode, int position) - { - 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) - { - 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, TextureInstructionSuffix textureInstructionSuffix, bool gprHandle) - { - bool isArray = opCode.HasArray(); - - GalTextureTarget textureTarget = TexToTextureTarget(opCode.Read(28, 6), isArray); - - bool hasDepthCompare = opCode.Read(0x32); - - if (hasDepthCompare) - { - textureInstructionSuffix |= TextureInstructionSuffix.Dc; - } - - 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] = 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++) - { - if (!IsChannelUsed(chMask, ch)) - { - continue; - } - - ShaderIrOperGpr dst = opCode.Gpr0(); - - dst.Index += regInc++; - - if (!dst.IsValidRegister || dst.IsConst) - { - continue; - } - - ShaderIrMetaTex meta = new ShaderIrMetaTex(ch, textureTarget, textureInstructionSuffix, coords) - { - LevelOfDetail = levelOfDetail, - Offset = offset, - DepthCompare = depthCompare - }; - - ShaderIrOp op = new ShaderIrOp(inst, coords[0], coords.Length > 1 ? coords[1] : null, operC, meta); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(dst, op))); - } - } - - public static void Texs(ShaderIrBlock block, long opCode, int position) - { - 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) - { - 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, - GalTextureTarget textureTarget, - TextureInstructionSuffix textureInstructionSuffix) - { - 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; - lutIndex |= !opCode.Gpr28().IsConst ? 2 : 0; - - if (lutIndex == 0) - { - //Both destination registers are RZ, do nothing. - return; - } - - bool fp16 = !opCode.Read(59); - - int dstIncrement = 0; - - ShaderIrOperGpr GetDst() - { - ShaderIrOperGpr dst; - - if (fp16) - { - //FP16 mode, two components are packed on the two - //halfs of a 32-bits register, as two half-float values. - int halfPart = dstIncrement & 1; - - switch (lutIndex) - { - case 1: dst = opCode.GprHalf0(halfPart); break; - case 2: dst = opCode.GprHalf28(halfPart); break; - case 3: dst = (dstIncrement >> 1) != 0 - ? opCode.GprHalf28(halfPart) - : opCode.GprHalf0(halfPart); break; - - default: throw new InvalidOperationException(); - } - } - else - { - //32-bits mode, each component uses one register. - //Two components uses two consecutive registers. - switch (lutIndex) - { - case 1: dst = opCode.Gpr0(); break; - case 2: dst = opCode.Gpr28(); break; - case 3: dst = (dstIncrement >> 1) != 0 - ? opCode.Gpr28() - : opCode.Gpr0(); break; - - default: throw new InvalidOperationException(); - } - - dst.Index += dstIncrement & 1; - } - - dstIncrement++; - - return dst; - } - - int chMask = _maskLut[lutIndex, opCode.Read(50, 7)]; - - if (chMask == 0) - { - //All channels are disabled, do nothing. - return; - } - - ShaderIrNode operC = opCode.Imm13_36(); - coords = CoordsRegistersToTempRegisters(block, coords); - - for (int ch = 0; ch < 4; ch++) - { - if (!IsChannelUsed(chMask, ch)) - { - continue; - } - - 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(); - - if (dst.IsValidRegister && !dst.IsConst) - { - block.AddNode(opCode.PredNode(new ShaderIrAsg(dst, op))); - } - } - } - - 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/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs deleted file mode 100644 index 0a2b4232b..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs +++ /dev/null @@ -1,431 +0,0 @@ -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 F2f_C(ShaderIrBlock block, long opCode, int position) - { - EmitF2F(block, opCode, ShaderOper.Cr); - } - - public static void F2f_I(ShaderIrBlock block, long opCode, int position) - { - EmitF2F(block, opCode, ShaderOper.Immf); - } - - public static void F2f_R(ShaderIrBlock block, long opCode, int position) - { - EmitF2F(block, opCode, ShaderOper.Rr); - } - - public static void F2i_C(ShaderIrBlock block, long opCode, int position) - { - EmitF2I(block, opCode, ShaderOper.Cr); - } - - public static void F2i_I(ShaderIrBlock block, long opCode, int position) - { - EmitF2I(block, opCode, ShaderOper.Immf); - } - - public static void F2i_R(ShaderIrBlock block, long opCode, int position) - { - EmitF2I(block, opCode, ShaderOper.Rr); - } - - public static void I2f_C(ShaderIrBlock block, long opCode, int position) - { - EmitI2F(block, opCode, ShaderOper.Cr); - } - - public static void I2f_I(ShaderIrBlock block, long opCode, int position) - { - EmitI2F(block, opCode, ShaderOper.Imm); - } - - public static void I2f_R(ShaderIrBlock block, long opCode, int position) - { - EmitI2F(block, opCode, ShaderOper.Rr); - } - - public static void I2i_C(ShaderIrBlock block, long opCode, int position) - { - EmitI2I(block, opCode, ShaderOper.Cr); - } - - public static void I2i_I(ShaderIrBlock block, long opCode, int position) - { - EmitI2I(block, opCode, ShaderOper.Imm); - } - - public static void I2i_R(ShaderIrBlock block, long opCode, int position) - { - EmitI2I(block, opCode, ShaderOper.Rr); - } - - public static void Isberd(ShaderIrBlock block, long opCode, int position) - { - //This instruction seems to be used to translate from an address to a vertex index in a GS - //Stub it as such - - block.AddNode(new ShaderIrCmnt("Stubbed.")); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), opCode.Gpr8()))); - } - - public static void Mov_C(ShaderIrBlock block, long opCode, int position) - { - ShaderIrOperCbuf cbuf = opCode.Cbuf34(); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), cbuf))); - } - - public static void Mov_I(ShaderIrBlock block, long opCode, int position) - { - ShaderIrOperImm imm = opCode.Imm19_20(); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), imm))); - } - - public static void Mov_I32(ShaderIrBlock block, long opCode, int position) - { - ShaderIrOperImm imm = opCode.Imm32_20(); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), imm))); - } - - public static void Mov_R(ShaderIrBlock block, long opCode, int position) - { - ShaderIrOperGpr gpr = opCode.Gpr20(); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), gpr))); - } - - public static void Sel_C(ShaderIrBlock block, long opCode, int position) - { - EmitSel(block, opCode, ShaderOper.Cr); - } - - public static void Sel_I(ShaderIrBlock block, long opCode, int position) - { - EmitSel(block, opCode, ShaderOper.Imm); - } - - public static void Sel_R(ShaderIrBlock block, long opCode, int position) - { - EmitSel(block, opCode, ShaderOper.Rr); - } - - public static void Mov_S(ShaderIrBlock block, long opCode, int position) - { - block.AddNode(new ShaderIrCmnt("Stubbed.")); - - //Zero is used as a special number to get a valid "0 * 0 + VertexIndex" in a GS - ShaderIrNode source = new ShaderIrOperImm(0); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), source))); - } - - private static void EmitF2F(ShaderIrBlock block, long opCode, ShaderOper oper) - { - bool negA = opCode.Read(45); - bool absA = opCode.Read(49); - - ShaderIrNode operA; - - switch (oper) - { - case ShaderOper.Cr: operA = opCode.Cbuf34(); break; - case ShaderOper.Immf: operA = opCode.Immf19_20(); break; - case ShaderOper.Rr: operA = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operA = GetAluFabsFneg(operA, absA, negA); - - ShaderIrInst roundInst = GetRoundInst(opCode); - - if (roundInst != ShaderIrInst.Invalid) - { - operA = new ShaderIrOp(roundInst, operA); - } - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operA))); - } - - private static void EmitF2I(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(); - } - - bool negA = opCode.Read(45); - bool absA = opCode.Read(49); - - ShaderIrNode operA; - - switch (oper) - { - case ShaderOper.Cr: operA = opCode.Cbuf34(); break; - case ShaderOper.Immf: operA = opCode.Immf19_20(); break; - case ShaderOper.Rr: operA = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operA = GetAluFabsFneg(operA, absA, negA); - - ShaderIrInst roundInst = GetRoundInst(opCode); - - if (roundInst != ShaderIrInst.Invalid) - { - operA = new ShaderIrOp(roundInst, operA); - } - - bool signed = type >= IntType.S8; - - int size = 8 << ((int)type & 3); - - if (size < 32) - { - uint mask = uint.MaxValue >> (32 - size); - - float cMin = 0; - float cMax = mask; - - if (signed) - { - uint halfMask = mask >> 1; - - cMin -= halfMask + 1; - cMax = halfMask; - } - - ShaderIrOperImmf min = new ShaderIrOperImmf(cMin); - ShaderIrOperImmf max = new ShaderIrOperImmf(cMax); - - operA = new ShaderIrOp(ShaderIrInst.Fclamp, operA, min, max); - } - - ShaderIrInst inst = signed - ? ShaderIrInst.Ftos - : ShaderIrInst.Ftou; - - ShaderIrNode op = new ShaderIrOp(inst, operA); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - 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 = opCode.Read(41, 3); - - bool negA = opCode.Read(45); - bool absA = opCode.Read(49); - - ShaderIrNode operA; - - switch (oper) - { - case ShaderOper.Cr: operA = opCode.Cbuf34(); break; - case ShaderOper.Imm: operA = opCode.Imm19_20(); break; - case ShaderOper.Rr: operA = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operA = GetAluIabsIneg(operA, absA, negA); - - bool signed = type >= IntType.S8; - - int shift = sel * 8; - - int size = 8 << ((int)type & 3); - - if (shift != 0) - { - operA = new ShaderIrOp(ShaderIrInst.Asr, operA, new ShaderIrOperImm(shift)); - } - - if (size < 32) - { - operA = ExtendTo32(operA, signed, size); - } - - ShaderIrInst inst = signed - ? ShaderIrInst.Stof - : ShaderIrInst.Utof; - - ShaderIrNode op = new ShaderIrOp(inst, operA); - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op))); - } - - private static void EmitI2I(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 = opCode.Read(41, 3); - - bool negA = opCode.Read(45); - bool absA = opCode.Read(49); - bool satA = opCode.Read(50); - - ShaderIrNode operA; - - switch (oper) - { - case ShaderOper.Cr: operA = opCode.Cbuf34(); break; - case ShaderOper.Immf: operA = opCode.Immf19_20(); break; - case ShaderOper.Rr: operA = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - operA = GetAluIabsIneg(operA, absA, negA); - - bool signed = type >= IntType.S8; - - int shift = sel * 8; - - int size = 8 << ((int)type & 3); - - if (shift != 0) - { - operA = new ShaderIrOp(ShaderIrInst.Asr, operA, new ShaderIrOperImm(shift)); - } - - if (size < 32) - { - uint mask = uint.MaxValue >> (32 - size); - - if (satA) - { - uint cMin = 0; - uint cMax = mask; - - if (signed) - { - uint halfMask = mask >> 1; - - cMin -= halfMask + 1; - cMax = halfMask; - } - - ShaderIrOperImm min = new ShaderIrOperImm((int)cMin); - ShaderIrOperImm max = new ShaderIrOperImm((int)cMax); - - operA = new ShaderIrOp(signed - ? ShaderIrInst.Clamps - : ShaderIrInst.Clampu, operA, min, max); - } - else - { - operA = ExtendTo32(operA, signed, size); - } - } - - block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operA))); - } - - private static void EmitSel(ShaderIrBlock block, long opCode, ShaderOper oper) - { - ShaderIrOperGpr dst = opCode.Gpr0(); - ShaderIrNode pred = opCode.Pred39N(); - - ShaderIrNode resultA = opCode.Gpr8(); - ShaderIrNode resultB; - - switch (oper) - { - case ShaderOper.Cr: resultB = opCode.Cbuf34(); break; - case ShaderOper.Imm: resultB = opCode.Imm19_20(); break; - case ShaderOper.Rr: resultB = opCode.Gpr20(); break; - - default: throw new ArgumentException(nameof(oper)); - } - - block.AddNode(opCode.PredNode(new ShaderIrCond(pred, new ShaderIrAsg(dst, resultA), false))); - - block.AddNode(opCode.PredNode(new ShaderIrCond(pred, new ShaderIrAsg(dst, resultB), true))); - } - - private static IntType GetIntType(long opCode) - { - bool signed = opCode.Read(13); - - IntType type = (IntType)(opCode.Read(10, 3)); - - if (signed) - { - type += (int)IntType.S8; - } - - return type; - } - - private static FloatType GetFloatType(long opCode) - { - return (FloatType)(opCode.Read(8, 3)); - } - - private static ShaderIrInst GetRoundInst(long opCode) - { - switch (opCode.Read(39, 3)) - { - case 1: return ShaderIrInst.Floor; - case 2: return ShaderIrInst.Ceil; - case 3: return ShaderIrInst.Trunc; - } - - return ShaderIrInst.Invalid; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs deleted file mode 100644 index 4b1e40469..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - private static int Read(this long opCode, int position, int mask) - { - return (int)(opCode >> position) & mask; - } - - private static bool Read(this long opCode, int position) - { - return ((opCode >> position) & 1) != 0; - } - - private static int Branch(this long opCode) - { - 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); - int size = opCode.Read(47, 3); - - ShaderIrOperGpr vertex = opCode.Gpr39(); - - ShaderIrOperAbuf[] opers = new ShaderIrOperAbuf[size + 1]; - - for (int index = 0; index <= size; index++) - { - opers[index] = new ShaderIrOperAbuf(abuf + index * 4, vertex); - } - - return opers; - } - - private static ShaderIrOperAbuf Abuf28(this long opCode) - { - int abuf = opCode.Read(28, 0x3ff); - - return new ShaderIrOperAbuf(abuf, opCode.Gpr39()); - } - - private static ShaderIrOperCbuf Cbuf34(this long opCode) - { - return new ShaderIrOperCbuf( - opCode.Read(34, 0x1f), - opCode.Read(20, 0x3fff)); - } - - private static ShaderIrOperGpr Gpr8(this long opCode) - { - return new ShaderIrOperGpr(opCode.Read(8, 0xff)); - } - - private static ShaderIrOperGpr Gpr20(this long opCode) - { - return new ShaderIrOperGpr(opCode.Read(20, 0xff)); - } - - private static ShaderIrOperGpr Gpr39(this long opCode) - { - return new ShaderIrOperGpr(opCode.Read(39, 0xff)); - } - - private static ShaderIrOperGpr Gpr0(this long opCode) - { - return new ShaderIrOperGpr(opCode.Read(0, 0xff)); - } - - private static ShaderIrOperGpr Gpr28(this long opCode) - { - return new ShaderIrOperGpr(opCode.Read(28, 0xff)); - } - - private static ShaderIrOperGpr[] GprHalfVec8(this long opCode) - { - return GetGprHalfVec2(opCode.Read(8, 0xff), opCode.Read(47, 3)); - } - - private static ShaderIrOperGpr[] GprHalfVec20(this long opCode) - { - return GetGprHalfVec2(opCode.Read(20, 0xff), opCode.Read(28, 3)); - } - - private static ShaderIrOperGpr[] GetGprHalfVec2(int gpr, int mask) - { - if (mask == 1) - { - //This value is used for FP32, the whole 32-bits register - //is used as each element on the vector. - return new ShaderIrOperGpr[] - { - new ShaderIrOperGpr(gpr), - new ShaderIrOperGpr(gpr) - }; - } - - ShaderIrOperGpr low = new ShaderIrOperGpr(gpr, 0); - ShaderIrOperGpr high = new ShaderIrOperGpr(gpr, 1); - - return new ShaderIrOperGpr[] - { - (mask & 1) != 0 ? high : low, - (mask & 2) != 0 ? high : low - }; - } - - private static ShaderIrOperGpr GprHalf0(this long opCode, int halfPart) - { - return new ShaderIrOperGpr(opCode.Read(0, 0xff), halfPart); - } - - private static ShaderIrOperGpr GprHalf28(this long opCode, int halfPart) - { - return new ShaderIrOperGpr(opCode.Read(28, 0xff), halfPart); - } - - private static ShaderIrOperImm Imm5_39(this long opCode) - { - return new ShaderIrOperImm(opCode.Read(39, 0x1f)); - } - - private static ShaderIrOperImm Imm13_36(this long opCode) - { - return new ShaderIrOperImm(opCode.Read(36, 0x1fff)); - } - - private static ShaderIrOperImm Imm32_20(this long opCode) - { - return new ShaderIrOperImm((int)(opCode >> 20)); - } - - private static ShaderIrOperImmf Immf32_20(this long opCode) - { - return new ShaderIrOperImmf(BitConverter.Int32BitsToSingle((int)(opCode >> 20))); - } - - private static ShaderIrOperImm ImmU16_20(this long opCode) - { - return new ShaderIrOperImm(opCode.Read(20, 0xffff)); - } - - private static ShaderIrOperImm Imm19_20(this long opCode) - { - int value = opCode.Read(20, 0x7ffff); - - bool neg = opCode.Read(56); - - if (neg) - { - value = -value; - } - - return new ShaderIrOperImm(value); - } - - private static ShaderIrOperImmf Immf19_20(this long opCode) - { - uint imm = (uint)(opCode >> 20) & 0x7ffff; - - bool neg = opCode.Read(56); - - imm <<= 12; - - if (neg) - { - imm |= 0x80000000; - } - - float value = BitConverter.Int32BitsToSingle((int)imm); - - return new ShaderIrOperImmf(value); - } - - private static ShaderIrOperPred Pred0(this long opCode) - { - return new ShaderIrOperPred(opCode.Read(0, 7)); - } - - private static ShaderIrOperPred Pred3(this long opCode) - { - return new ShaderIrOperPred(opCode.Read(3, 7)); - } - - private static ShaderIrOperPred Pred12(this long opCode) - { - return new ShaderIrOperPred(opCode.Read(12, 7)); - } - - private static ShaderIrOperPred Pred29(this long opCode) - { - return new ShaderIrOperPred(opCode.Read(29, 7)); - } - - private static ShaderIrNode Pred39N(this long opCode) - { - ShaderIrNode node = opCode.Pred39(); - - if (opCode.Read(42)) - { - node = new ShaderIrOp(ShaderIrInst.Bnot, node); - } - - return node; - } - - private static ShaderIrOperPred Pred39(this long opCode) - { - return new ShaderIrOperPred(opCode.Read(39, 7)); - } - - private static ShaderIrOperPred Pred48(this long opCode) - { - return new ShaderIrOperPred(opCode.Read(48, 7)); - } - - private static ShaderIrInst Cmp(this long opCode) - { - switch (opCode.Read(49, 7)) - { - case 1: return ShaderIrInst.Clt; - case 2: return ShaderIrInst.Ceq; - case 3: return ShaderIrInst.Cle; - case 4: return ShaderIrInst.Cgt; - case 5: return ShaderIrInst.Cne; - case 6: return ShaderIrInst.Cge; - } - - throw new ArgumentException(nameof(opCode)); - } - - private static ShaderIrInst CmpF(this long opCode) - { - switch (opCode.Read(48, 0xf)) - { - case 0x1: return ShaderIrInst.Fclt; - case 0x2: return ShaderIrInst.Fceq; - case 0x3: return ShaderIrInst.Fcle; - case 0x4: return ShaderIrInst.Fcgt; - case 0x5: return ShaderIrInst.Fcne; - case 0x6: return ShaderIrInst.Fcge; - case 0x7: return ShaderIrInst.Fcnum; - case 0x8: return ShaderIrInst.Fcnan; - case 0x9: return ShaderIrInst.Fcltu; - case 0xa: return ShaderIrInst.Fcequ; - case 0xb: return ShaderIrInst.Fcleu; - case 0xc: return ShaderIrInst.Fcgtu; - case 0xd: return ShaderIrInst.Fcneu; - case 0xe: return ShaderIrInst.Fcgeu; - } - - throw new ArgumentException(nameof(opCode)); - } - - private static ShaderIrInst BLop45(this long opCode) - { - switch (opCode.Read(45, 3)) - { - case 0: return ShaderIrInst.Band; - case 1: return ShaderIrInst.Bor; - case 2: return ShaderIrInst.Bxor; - } - - throw new ArgumentException(nameof(opCode)); - } - - private static ShaderIrInst BLop24(this long opCode) - { - switch (opCode.Read(24, 3)) - { - case 0: return ShaderIrInst.Band; - case 1: return ShaderIrInst.Bor; - case 2: return ShaderIrInst.Bxor; - } - - throw new ArgumentException(nameof(opCode)); - } - - private static ShaderIrNode PredNode(this long opCode, ShaderIrNode node) - { - ShaderIrOperPred pred = opCode.PredNode(); - - if (pred.Index != ShaderIrOperPred.UnusedIndex) - { - bool inv = opCode.Read(19); - - node = new ShaderIrCond(pred, node, inv); - } - - return node; - } - - private static ShaderIrOperPred PredNode(this long opCode) - { - int pred = opCode.Read(16, 0xf); - - if (pred != 0xf) - { - pred &= 7; - } - - return new ShaderIrOperPred(pred); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs deleted file mode 100644 index 9098ca5e5..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - public static void Out_R(ShaderIrBlock block, long opCode, int position) - { - //TODO: Those registers have to be used for something - ShaderIrOperGpr gpr0 = opCode.Gpr0(); - ShaderIrOperGpr gpr8 = opCode.Gpr8(); - ShaderIrOperGpr gpr20 = opCode.Gpr20(); - - int type = opCode.Read(39, 3); - - if ((type & 1) != 0) - { - block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Emit))); - } - - if ((type & 2) != 0) - { - block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Cut))); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs deleted file mode 100644 index 4b23f8d0f..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static class ShaderDecoder - { - private const long HeaderSize = 0x50; - - private const bool AddDbgComments = true; - - public static ShaderIrBlock[] Decode(IGalMemory memory, long start) - { - Dictionary visited = new Dictionary(); - Dictionary visitedEnd = new Dictionary(); - - Queue blocks = new Queue(); - - long beginning = start + HeaderSize; - - ShaderIrBlock Enqueue(int position, ShaderIrBlock source = null) - { - if (!visited.TryGetValue(position, out ShaderIrBlock output)) - { - output = new ShaderIrBlock(position); - - blocks.Enqueue(output); - - visited.Add(position, output); - } - - if (source != null) - { - output.Sources.Add(source); - } - - return output; - } - - ShaderIrBlock entry = Enqueue(0); - - while (blocks.Count > 0) - { - ShaderIrBlock current = blocks.Dequeue(); - - FillBlock(memory, current, beginning); - - //Set child blocks. "Branch" is the block the branch instruction - //points to (when taken), "Next" is the block at the next address, - //executed when the branch is not taken. For Unconditional Branches - //or end of shader, Next is null. - if (current.Nodes.Count > 0) - { - ShaderIrNode lastNode = current.GetLastNode(); - - ShaderIrOp innerOp = GetInnermostOp(lastNode); - - if (innerOp?.Inst == ShaderIrInst.Bra) - { - int target = ((ShaderIrOperImm)innerOp.OperandA).Value; - - current.Branch = Enqueue(target, current); - } - - foreach (ShaderIrNode node in current.Nodes) - { - innerOp = GetInnermostOp(node); - - if (innerOp is ShaderIrOp currOp && currOp.Inst == ShaderIrInst.Ssy) - { - int target = ((ShaderIrOperImm)currOp.OperandA).Value; - - Enqueue(target, current); - } - } - - if (NodeHasNext(lastNode)) - { - current.Next = Enqueue(current.EndPosition); - } - } - - //If we have on the graph two blocks with the same end position, - //then we need to split the bigger block and have two small blocks, - //the end position of the bigger "Current" block should then be == to - //the position of the "Smaller" block. - while (visitedEnd.TryGetValue(current.EndPosition, out ShaderIrBlock smaller)) - { - if (current.Position > smaller.Position) - { - ShaderIrBlock temp = smaller; - - smaller = current; - current = temp; - } - - current.EndPosition = smaller.Position; - current.Next = smaller; - current.Branch = null; - - current.Nodes.RemoveRange( - current.Nodes.Count - smaller.Nodes.Count, - smaller.Nodes.Count); - - visitedEnd[smaller.EndPosition] = smaller; - } - - visitedEnd.Add(current.EndPosition, current); - } - - //Make and sort Graph blocks array by position. - ShaderIrBlock[] graph = new ShaderIrBlock[visited.Count]; - - while (visited.Count > 0) - { - uint firstPos = uint.MaxValue; - - foreach (ShaderIrBlock block in visited.Values) - { - if (firstPos > (uint)block.Position) - firstPos = (uint)block.Position; - } - - ShaderIrBlock current = visited[(int)firstPos]; - - do - { - graph[graph.Length - visited.Count] = current; - - visited.Remove(current.Position); - - current = current.Next; - } - while (current != null); - } - - return graph; - } - - private static void FillBlock(IGalMemory memory, ShaderIrBlock block, long beginning) - { - int position = block.Position; - - do - { - //Ignore scheduling instructions, which are written every 32 bytes. - if ((position & 0x1f) == 0) - { - position += 8; - - continue; - } - - uint word0 = (uint)memory.ReadInt32(position + beginning + 0); - uint word1 = (uint)memory.ReadInt32(position + beginning + 4); - - position += 8; - - long opCode = word0 | (long)word1 << 32; - - ShaderDecodeFunc decode = ShaderOpCodeTable.GetDecoder(opCode); - - if (AddDbgComments) - { - string dbgOpCode = $"0x{(position - 8):x16}: 0x{opCode:x16} "; - - dbgOpCode += (decode?.Method.Name ?? "???"); - - if (decode == ShaderDecode.Bra || decode == ShaderDecode.Ssy) - { - int offset = ((int)(opCode >> 20) << 8) >> 8; - - long target = position + offset; - - dbgOpCode += " (0x" + target.ToString("x16") + ")"; - } - - block.AddNode(new ShaderIrCmnt(dbgOpCode)); - } - - if (decode == null) - { - continue; - } - - decode(block, opCode, position); - } - while (!IsFlowChange(block.GetLastNode())); - - block.EndPosition = position; - } - - private static bool IsFlowChange(ShaderIrNode node) - { - return !NodeHasNext(GetInnermostOp(node)); - } - - private static ShaderIrOp GetInnermostOp(ShaderIrNode node) - { - if (node is ShaderIrCond cond) - { - node = cond.Child; - } - - return node is ShaderIrOp op ? op : null; - } - - private static bool NodeHasNext(ShaderIrNode node) - { - if (!(node is ShaderIrOp op)) - { - return true; - } - - return op.Inst != ShaderIrInst.Exit && - op.Inst != ShaderIrInst.Bra; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs deleted file mode 100644 index 2f9326e12..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Shader -{ - struct OmapTarget - { - public bool Red; - public bool Green; - public bool Blue; - public bool Alpha; - - public bool Enabled => Red || Green || Blue || Alpha; - - public bool ComponentEnabled(int component) - { - switch (component) - { - case 0: return Red; - case 1: return Green; - case 2: return Blue; - case 3: return Alpha; - } - - throw new ArgumentException(nameof(component)); - } - } - - class ShaderHeader - { - public const int PointList = 1; - public const int LineStrip = 6; - public const int TriangleStrip = 7; - - public int SphType { get; private set; } - public int Version { get; private set; } - public int ShaderType { get; private set; } - public bool MrtEnable { get; private set; } - public bool KillsPixels { get; private set; } - public bool DoesGlobalStore { get; private set; } - public int SassVersion { get; private set; } - public bool DoesLoadOrStore { get; private set; } - public bool DoesFp64 { get; private set; } - public int StreamOutMask { get; private set; } - - public int ShaderLocalMemoryLowSize { get; private set; } - public int PerPatchAttributeCount { get; private set; } - - public int ShaderLocalMemoryHighSize { get; private set; } - public int ThreadsPerInputPrimitive { get; private set; } - - public int ShaderLocalMemoryCrsSize { get; private set; } - public int OutputTopology { get; private set; } - - public int MaxOutputVertexCount { get; private set; } - public int StoreReqStart { get; private set; } - public int StoreReqEnd { get; private set; } - - public OmapTarget[] OmapTargets { get; private set; } - public bool OmapSampleMask { get; private set; } - public bool OmapDepth { get; private set; } - - public ShaderHeader(IGalMemory memory, long position) - { - uint commonWord0 = (uint)memory.ReadInt32(position + 0); - uint commonWord1 = (uint)memory.ReadInt32(position + 4); - uint commonWord2 = (uint)memory.ReadInt32(position + 8); - uint commonWord3 = (uint)memory.ReadInt32(position + 12); - uint commonWord4 = (uint)memory.ReadInt32(position + 16); - - SphType = ReadBits(commonWord0, 0, 5); - Version = ReadBits(commonWord0, 5, 5); - ShaderType = ReadBits(commonWord0, 10, 4); - MrtEnable = ReadBits(commonWord0, 14, 1) != 0; - KillsPixels = ReadBits(commonWord0, 15, 1) != 0; - DoesGlobalStore = ReadBits(commonWord0, 16, 1) != 0; - SassVersion = ReadBits(commonWord0, 17, 4); - DoesLoadOrStore = ReadBits(commonWord0, 26, 1) != 0; - DoesFp64 = ReadBits(commonWord0, 27, 1) != 0; - StreamOutMask = ReadBits(commonWord0, 28, 4); - - ShaderLocalMemoryLowSize = ReadBits(commonWord1, 0, 24); - PerPatchAttributeCount = ReadBits(commonWord1, 24, 8); - - ShaderLocalMemoryHighSize = ReadBits(commonWord2, 0, 24); - ThreadsPerInputPrimitive = ReadBits(commonWord2, 24, 8); - - ShaderLocalMemoryCrsSize = ReadBits(commonWord3, 0, 24); - OutputTopology = ReadBits(commonWord3, 24, 4); - - MaxOutputVertexCount = ReadBits(commonWord4, 0, 12); - StoreReqStart = ReadBits(commonWord4, 12, 8); - StoreReqEnd = ReadBits(commonWord4, 24, 8); - - //Type 2 (fragment?) reading - uint type2OmapTarget = (uint)memory.ReadInt32(position + 72); - uint type2Omap = (uint)memory.ReadInt32(position + 76); - - OmapTargets = new OmapTarget[8]; - - for (int i = 0; i < OmapTargets.Length; i++) - { - int offset = i * 4; - - OmapTargets[i] = new OmapTarget - { - Red = ReadBits(type2OmapTarget, offset + 0, 1) != 0, - Green = ReadBits(type2OmapTarget, offset + 1, 1) != 0, - Blue = ReadBits(type2OmapTarget, offset + 2, 1) != 0, - Alpha = ReadBits(type2OmapTarget, offset + 3, 1) != 0 - }; - } - - OmapSampleMask = ReadBits(type2Omap, 0, 1) != 0; - OmapDepth = ReadBits(type2Omap, 1, 1) != 0; - } - - public int DepthRegister - { - get - { - int count = 0; - - for (int index = 0; index < OmapTargets.Length; index++) - { - for (int component = 0; component < 4; component++) - { - if (OmapTargets[index].ComponentEnabled(component)) - { - count++; - } - } - } - - // Depth register is always two registers after the last color output - return count + 1; - } - } - - private static int ReadBits(uint word, int offset, int bitWidth) - { - uint mask = (1u << bitWidth) - 1u; - - return (int)((word >> offset) & mask); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs deleted file mode 100644 index b3713fa48..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderIpaMode - { - Pass = 0, - None = 1, - Constant = 2, - Sc = 3 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs deleted file mode 100644 index 53871a145..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrAsg : ShaderIrNode - { - public ShaderIrNode Dst { get; set; } - public ShaderIrNode Src { get; set; } - - public ShaderIrAsg(ShaderIrNode dst, ShaderIrNode src) - { - Dst = dst; - Src = src; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs deleted file mode 100644 index 49257d283..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrBlock - { - public int Position { get; set; } - public int EndPosition { get; set; } - - public ShaderIrBlock Next { get; set; } - public ShaderIrBlock Branch { get; set; } - - public List Sources { get; private set; } - - public List Nodes { get; private set; } - - public ShaderIrBlock(int position) - { - Position = position; - - Sources = new List(); - - Nodes = new List(); - } - - public void AddNode(ShaderIrNode node) - { - Nodes.Add(node); - } - - 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/ShaderIrCmnt.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs deleted file mode 100644 index 5da04e5ee..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrCmnt : ShaderIrNode - { - public string Comment { get; private set; } - - public ShaderIrCmnt(string comment) - { - Comment = comment; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs deleted file mode 100644 index 34acf90d7..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrCond : ShaderIrNode - { - public ShaderIrNode Pred { get; set; } - public ShaderIrNode Child { get; set; } - - public bool Not { get; private set; } - - public ShaderIrCond(ShaderIrNode pred, ShaderIrNode child, bool not) - { - Pred = pred; - Child = child; - Not = not; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs deleted file mode 100644 index 68ff214e4..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderIrInst - { - Invalid, - - B_Start, - Band, - Bnot, - Bor, - Bxor, - B_End, - - F_Start, - Ceil, - - Fabs, - Fadd, - Fceq, - Fcequ, - Fcge, - Fcgeu, - Fcgt, - Fcgtu, - Fclamp, - Fcle, - Fcleu, - Fclt, - Fcltu, - Fcnan, - Fcne, - Fcneu, - Fcnum, - Fcos, - Fex2, - Ffma, - Flg2, - Floor, - Fmax, - Fmin, - Fmul, - Fneg, - Frcp, - Frsq, - Fsin, - Fsqrt, - Ftos, - Ftou, - Ipa, - Texb, - Texs, - Tld4, - Trunc, - F_End, - - I_Start, - Abs, - Add, - And, - Asr, - Ceq, - Cge, - Cgt, - Clamps, - Clampu, - Cle, - Clt, - Cne, - Lsl, - Lsr, - Max, - Min, - Mul, - Neg, - Not, - Or, - Stof, - Sub, - Texq, - Txlf, - Utof, - Xor, - I_End, - - Bra, - Exit, - Kil, - Ssy, - Sync, - - Emit, - Cut - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs deleted file mode 100644 index afb7503be..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrMeta { } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs deleted file mode 100644 index 07db64675..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrMetaIpa : ShaderIrMeta - { - public ShaderIpaMode Mode { get; private set; } - - public ShaderIrMetaIpa(ShaderIpaMode mode) - { - Mode = mode; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs deleted file mode 100644 index e0265138c..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Ryujinx.Graphics.Texture; - -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrMetaTex : ShaderIrMeta - { - 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, GalTextureTarget textureTarget, TextureInstructionSuffix textureInstructionSuffix, params ShaderIrNode[] coordinates) - { - Elem = elem; - TextureTarget = textureTarget; - TextureInstructionSuffix = textureInstructionSuffix; - Coordinates = coordinates; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs deleted file mode 100644 index c925ea4e1..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrMetaTexq : ShaderIrMeta - { - public ShaderTexqInfo Info { get; private set; } - - public int Elem { get; private set; } - - public ShaderIrMetaTexq(ShaderTexqInfo info, int elem) - { - Info = info; - Elem = elem; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs deleted file mode 100644 index 2648164a1..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index c91c39265..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs +++ /dev/null @@ -1,25 +0,0 @@ -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 ShaderIrMeta MetaData { get; set; } - - public ShaderIrOp( - ShaderIrInst inst, - ShaderIrNode operandA = null, - ShaderIrNode operandB = null, - ShaderIrNode operandC = null, - ShaderIrMeta metaData = null) - { - Inst = inst; - OperandA = operandA; - OperandB = operandB; - OperandC = operandC; - MetaData = metaData; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs deleted file mode 100644 index 1f339e805..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperAbuf : ShaderIrNode - { - public int Offs { get; private set; } - - public ShaderIrNode Vertex { get; private set; } - - public ShaderIrOperAbuf(int offs, ShaderIrNode vertex) - { - Offs = offs; - Vertex = vertex; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs deleted file mode 100644 index 9f419bbbe..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperCbuf : ShaderIrNode - { - public int Index { get; private set; } - public int Pos { get; set; } - - public ShaderIrNode Offs { get; private set; } - - public ShaderIrOperCbuf(int index, int pos, ShaderIrNode offs = null) - { - Index = index; - Pos = pos; - Offs = offs; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs deleted file mode 100644 index 0d102d897..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperGpr : ShaderIrNode - { - public const int ZrIndex = 0xff; - - public bool IsConst => Index == ZrIndex; - - public bool IsValidRegister => (uint)Index <= ZrIndex; - - public int Index { get; set; } - public int HalfPart { get; set; } - - public ShaderRegisterSize RegisterSize { get; private set; } - - public ShaderIrOperGpr(int index) - { - Index = index; - - RegisterSize = ShaderRegisterSize.Single; - } - - public ShaderIrOperGpr(int index, int halfPart) - { - Index = index; - HalfPart = halfPart; - - RegisterSize = ShaderRegisterSize.Half; - } - - public static ShaderIrOperGpr MakeTemporary(int index = 0) - { - return new ShaderIrOperGpr(0x100 + index); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs deleted file mode 100644 index 6b23b3657..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperImm : ShaderIrNode - { - public int Value { get; private set; } - - public ShaderIrOperImm(int value) - { - Value = value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs deleted file mode 100644 index 5b08c5b1c..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperImmf : ShaderIrNode - { - public float Value { get; private set; } - - public ShaderIrOperImmf(float value) - { - Value = value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs deleted file mode 100644 index 6c16a145d..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs +++ /dev/null @@ -1,17 +0,0 @@ -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) - { - Index = index; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs deleted file mode 100644 index 1edf91a01..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static class ShaderOpCodeTable - { - private const int EncodingBits = 14; - - private class ShaderDecodeEntry - { - public ShaderDecodeFunc Func; - - public int XBits; - - public ShaderDecodeEntry(ShaderDecodeFunc func, int xBits) - { - Func = func; - XBits = xBits; - } - } - - private static ShaderDecodeEntry[] _opCodes; - - static ShaderOpCodeTable() - { - _opCodes = new ShaderDecodeEntry[1 << EncodingBits]; - -#region Instructions - Set("0100110000000x", ShaderDecode.Bfe_C); - Set("0011100x00000x", ShaderDecode.Bfe_I); - Set("0101110000000x", ShaderDecode.Bfe_R); - Set("111000100100xx", ShaderDecode.Bra); - Set("111000110000xx", ShaderDecode.Exit); - Set("0100110010101x", ShaderDecode.F2f_C); - Set("0011100x10101x", ShaderDecode.F2f_I); - Set("0101110010101x", ShaderDecode.F2f_R); - Set("0100110010110x", ShaderDecode.F2i_C); - Set("0011100x10110x", ShaderDecode.F2i_I); - Set("0101110010110x", ShaderDecode.F2i_R); - Set("0100110001011x", ShaderDecode.Fadd_C); - Set("0011100x01011x", ShaderDecode.Fadd_I); - Set("000010xxxxxxxx", ShaderDecode.Fadd_I32); - Set("0101110001011x", ShaderDecode.Fadd_R); - Set("010010011xxxxx", ShaderDecode.Ffma_CR); - Set("0011001x1xxxxx", ShaderDecode.Ffma_I); - Set("010100011xxxxx", ShaderDecode.Ffma_RC); - Set("010110011xxxxx", ShaderDecode.Ffma_RR); - Set("0100110001101x", ShaderDecode.Fmul_C); - Set("0011100x01101x", ShaderDecode.Fmul_I); - Set("00011110xxxxxx", ShaderDecode.Fmul_I32); - Set("0101110001101x", ShaderDecode.Fmul_R); - Set("0100110001100x", ShaderDecode.Fmnmx_C); - Set("0011100x01100x", ShaderDecode.Fmnmx_I); - Set("0101110001100x", ShaderDecode.Fmnmx_R); - Set("0100100xxxxxxx", ShaderDecode.Fset_C); - Set("0011000xxxxxxx", ShaderDecode.Fset_I); - Set("01011000xxxxxx", ShaderDecode.Fset_R); - Set("010010111011xx", ShaderDecode.Fsetp_C); - Set("0011011x1011xx", ShaderDecode.Fsetp_I); - Set("010110111011xx", ShaderDecode.Fsetp_R); - Set("0101110100010x", ShaderDecode.Hadd2_R); - Set("0101110100001x", ShaderDecode.Hmul2_R); - Set("0100110010111x", ShaderDecode.I2f_C); - Set("0011100x10111x", ShaderDecode.I2f_I); - Set("0101110010111x", ShaderDecode.I2f_R); - Set("0100110011100x", ShaderDecode.I2i_C); - Set("0011100x11100x", ShaderDecode.I2i_I); - Set("0101110011100x", ShaderDecode.I2i_R); - Set("0100110000010x", ShaderDecode.Iadd_C); - Set("0011100000010x", ShaderDecode.Iadd_I); - Set("0001110x0xxxxx", ShaderDecode.Iadd_I32); - Set("0101110000010x", ShaderDecode.Iadd_R); - Set("010011001100xx", ShaderDecode.Iadd3_C); - Set("001110001100xx", ShaderDecode.Iadd3_I); - Set("010111001100xx", ShaderDecode.Iadd3_R); - Set("0100110000100x", ShaderDecode.Imnmx_C); - Set("0011100x00100x", ShaderDecode.Imnmx_I); - Set("0101110000100x", ShaderDecode.Imnmx_R); - Set("1110111111010x", ShaderDecode.Isberd); - Set("11100000xxxxxx", ShaderDecode.Ipa); - Set("0100110000011x", ShaderDecode.Iscadd_C); - Set("0011100x00011x", ShaderDecode.Iscadd_I); - Set("0101110000011x", ShaderDecode.Iscadd_R); - Set("010010110101xx", ShaderDecode.Iset_C); - Set("001101100101xx", ShaderDecode.Iset_I); - Set("010110110101xx", ShaderDecode.Iset_R); - Set("010010110110xx", ShaderDecode.Isetp_C); - Set("0011011x0110xx", ShaderDecode.Isetp_I); - Set("010110110110xx", ShaderDecode.Isetp_R); - Set("111000110011xx", ShaderDecode.Kil); - Set("1110111111011x", ShaderDecode.Ld_A); - Set("1110111110010x", ShaderDecode.Ld_C); - Set("0100110001000x", ShaderDecode.Lop_C); - Set("0011100001000x", ShaderDecode.Lop_I); - Set("000001xxxxxxxx", ShaderDecode.Lop_I32); - Set("0101110001000x", ShaderDecode.Lop_R); - Set("0100110010011x", ShaderDecode.Mov_C); - Set("0011100x10011x", ShaderDecode.Mov_I); - Set("000000010000xx", ShaderDecode.Mov_I32); - Set("0101110010011x", ShaderDecode.Mov_R); - Set("1111000011001x", ShaderDecode.Mov_S); - Set("0101000010000x", ShaderDecode.Mufu); - Set("1111101111100x", ShaderDecode.Out_R); - Set("0101000010010x", ShaderDecode.Psetp); - Set("0100110010010x", ShaderDecode.Rro_C); - Set("0011100x10010x", ShaderDecode.Rro_I); - Set("0101110010010x", ShaderDecode.Rro_R); - Set("0100110010100x", ShaderDecode.Sel_C); - Set("0011100010100x", ShaderDecode.Sel_I); - Set("0101110010100x", ShaderDecode.Sel_R); - Set("0100110001001x", ShaderDecode.Shl_C); - Set("0011100x01001x", ShaderDecode.Shl_I); - Set("0101110001001x", ShaderDecode.Shl_R); - Set("0100110000101x", ShaderDecode.Shr_C); - Set("0011100x00101x", ShaderDecode.Shr_I); - Set("0101110000101x", ShaderDecode.Shr_R); - Set("111000101001xx", ShaderDecode.Ssy); - Set("1110111111110x", ShaderDecode.St_A); - Set("1111000011111x", ShaderDecode.Sync); - Set("110000xxxx111x", ShaderDecode.Tex); - Set("1101111010111x", ShaderDecode.Tex_B); - 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); - Set("010100010xxxxx", ShaderDecode.Xmad_RC); - Set("0101101100xxxx", ShaderDecode.Xmad_RR); -#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; - - ShaderDecodeEntry entry = new ShaderDecodeEntry(func, xBits); - - for (int index = 0; index < (1 << xBits); index++) - { - value &= xMask; - - for (int x = 0; x < xBits; x++) - { - value |= ((index >> x) & 1) << xPos[x]; - } - - if (_opCodes[value] == null || _opCodes[value].XBits > xBits) - { - _opCodes[value] = entry; - } - } - } - - public static ShaderDecodeFunc GetDecoder(long opCode) - { - return _opCodes[(ulong)opCode >> (64 - EncodingBits)]?.Func; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs deleted file mode 100644 index 22a2ab85c..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderOper - { - Cr, - Imm, - Immf, - Rc, - Rr - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderRegisterSize.cs b/Ryujinx.Graphics/Gal/Shader/ShaderRegisterSize.cs deleted file mode 100644 index eb37359bf..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderRegisterSize.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderRegisterSize - { - Half, - Single, - Double - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs b/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs deleted file mode 100644 index 9158662cc..000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderTexqInfo - { - Dimension = 1, - TextureType = 2, - SamplePos = 5, - Filter = 16, - Lod = 18, - Wrap = 20, - BorderColor = 22 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs deleted file mode 100644 index f1f4650c8..000000000 --- a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Ryujinx.Graphics.Texture; - -namespace Ryujinx.Graphics.Gal -{ - public class ShaderDeclInfo - { - public string Name { get; private set; } - - public int Index { get; private set; } - public bool IsCb { get; private set; } - public int Cbuf { get; private set; } - public int Size { get; private set; } - - public GalTextureTarget TextureTarget { get; private set; } - - public TextureInstructionSuffix TextureSuffix { get; private set; } - - public ShaderDeclInfo( - string name, - int index, - bool isCb = false, - int cbuf = 0, - int size = 1, - GalTextureTarget textureTarget = GalTextureTarget.TwoD, - TextureInstructionSuffix textureSuffix = TextureInstructionSuffix.None) - { - Name = name; - Index = index; - IsCb = isCb; - Cbuf = cbuf; - Size = size; - - TextureTarget = textureTarget; - TextureSuffix = textureSuffix; - } - - internal void Enlarge(int newSize) - { - if (Size < newSize) - { - Size = newSize; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs index 605cbda81..bbed642b8 100644 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs @@ -1,6 +1,7 @@ using Ryujinx.Common; using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Texture; using System; using System.Collections.Generic; @@ -464,7 +465,7 @@ namespace Ryujinx.Graphics.Graphics3d left = _viewportX1 - (left - _viewportX0); right = _viewportX1 - (right - _viewportX0); } - + // Ensure X is in the right order if (left > right) { @@ -626,20 +627,22 @@ namespace Ryujinx.Graphics.Graphics3d for (int index = 0; index < keys.Length; index++) { - foreach (ShaderDeclInfo declInfo in _gpu.Renderer.Shader.GetTextureUsage(keys[index])) + foreach (TextureDescriptor desc in _gpu.Renderer.Shader.GetTextureUsage(keys[index])) { - long position; + int textureHandle; - if (declInfo.IsCb) + if (desc.IsBindless) { - position = _constBuffers[index][declInfo.Cbuf].Position; + long position = _constBuffers[index][desc.CbufSlot].Position; + + textureHandle = vmm.ReadInt32(position + desc.CbufOffset * 4); } else { - position = _constBuffers[index][textureCbIndex].Position; - } + long position = _constBuffers[index][textureCbIndex].Position; - int textureHandle = vmm.ReadInt32(position + declInfo.Index * 4); + textureHandle = vmm.ReadInt32(position + desc.HandleIndex * 4); + } unboundTextures.Add(UploadTexture(vmm, textureHandle)); } @@ -712,9 +715,9 @@ namespace Ryujinx.Graphics.Graphics3d { for (int stage = 0; stage < keys.Length; stage++) { - foreach (ShaderDeclInfo declInfo in _gpu.Renderer.Shader.GetConstBufferUsage(keys[stage])) + foreach (CBufferDescriptor desc in _gpu.Renderer.Shader.GetConstBufferUsage(keys[stage])) { - ConstBuffer cb = _constBuffers[stage][declInfo.Cbuf]; + ConstBuffer cb = _constBuffers[stage][desc.Slot]; if (!cb.Enabled) { @@ -735,7 +738,7 @@ namespace Ryujinx.Graphics.Graphics3d } } - state.ConstBufferKeys[stage][declInfo.Cbuf] = key; + state.ConstBufferKeys[stage][desc.Slot] = key; } } } diff --git a/Ryujinx.Graphics/Shader/CBufferDescriptor.cs b/Ryujinx.Graphics/Shader/CBufferDescriptor.cs new file mode 100644 index 000000000..f99665e16 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CBufferDescriptor.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader +{ + public struct CBufferDescriptor + { + public string Name { get; } + + public int Slot { get; } + + public CBufferDescriptor(string name, int slot) + { + Name = name; + Slot = slot; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs new file mode 100644 index 000000000..ce5d7b949 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class CodeGenContext + { + private const string Tab = " "; + + public ShaderConfig Config { get; } + + public List CBufferDescriptors { get; } + public List TextureDescriptors { get; } + + public OperandManager OperandManager { get; } + + private StringBuilder _sb; + + private int _level; + + private string _identation; + + public CodeGenContext(ShaderConfig config) + { + Config = config; + + CBufferDescriptors = new List(); + TextureDescriptors = new List(); + + OperandManager = new OperandManager(); + + _sb = new StringBuilder(); + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string str) + { + _sb.AppendLine(_identation + str); + } + + public string GetCode() + { + return _sb.ToString(); + } + + public void EnterScope() + { + AppendLine("{"); + + _level++; + + UpdateIdentation(); + } + + public void LeaveScope(string suffix = "") + { + if (_level == 0) + { + return; + } + + _level--; + + UpdateIdentation(); + + AppendLine("}" + suffix); + } + + private void UpdateIdentation() + { + _identation = GetIdentation(_level); + } + + private static string GetIdentation(int level) + { + string identation = string.Empty; + + for (int index = 0; index < level; index++) + { + identation += Tab; + } + + return identation; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs new file mode 100644 index 000000000..5412d8728 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs @@ -0,0 +1,206 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class Declarations + { + public static void Declare(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine("#version 420 core"); + + context.AppendLine(); + + context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;"); + + context.AppendLine(); + + if (context.Config.Type == GalShaderType.Geometry) + { + context.AppendLine("layout (points) in;"); + context.AppendLine("layout (triangle_strip, max_vertices = 4) out;"); + + context.AppendLine(); + } + + context.AppendLine("layout (std140) uniform Extra"); + + context.EnterScope(); + + context.AppendLine("vec2 flip;"); + context.AppendLine("int instance;"); + + context.LeaveScope(";"); + + context.AppendLine(); + + if (info.CBuffers.Count != 0) + { + DeclareUniforms(context, info); + + context.AppendLine(); + } + + if (info.Samplers.Count != 0) + { + DeclareSamplers(context, info); + + context.AppendLine(); + } + + if (info.IAttributes.Count != 0) + { + DeclareInputAttributes(context, info); + + context.AppendLine(); + } + + if (info.OAttributes.Count != 0) + { + DeclareOutputAttributes(context, info); + + context.AppendLine(); + } + } + + public static void DeclareLocals(CodeGenContext context, StructuredProgramInfo info) + { + foreach (AstOperand decl in info.Locals) + { + string name = context.OperandManager.DeclareLocal(decl); + + context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";"); + } + } + + private static string GetVarTypeName(VariableType type) + { + switch (type) + { + case VariableType.Bool: return "bool"; + case VariableType.F32: return "float"; + case VariableType.S32: return "int"; + case VariableType.U32: return "uint"; + } + + throw new ArgumentException($"Invalid variable type \"{type}\"."); + } + + private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int cbufSlot in info.CBuffers.OrderBy(x => x)) + { + string ubName = OperandManager.GetShaderStagePrefix(context.Config.Type); + + ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot; + + context.CBufferDescriptors.Add(new CBufferDescriptor(ubName, cbufSlot)); + + context.AppendLine("layout (std140) uniform " + ubName); + + context.EnterScope(); + + string ubSize = "[" + NumberFormatter.FormatInt(context.Config.MaxCBufferSize / 16) + "]"; + + context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Type, cbufSlot) + ubSize + ";"); + + context.LeaveScope(";"); + } + } + + private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo info) + { + Dictionary samplers = new Dictionary(); + + foreach (AstTextureOperation texOp in info.Samplers.OrderBy(x => x.Handle)) + { + string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp); + + if (!samplers.TryAdd(samplerName, texOp)) + { + continue; + } + + string samplerTypeName = GetSamplerTypeName(texOp.Type); + + context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";"); + } + + foreach (KeyValuePair kv in samplers) + { + string samplerName = kv.Key; + + AstTextureOperation texOp = kv.Value; + + TextureDescriptor desc; + + if ((texOp.Flags & TextureFlags.Bindless) != 0) + { + AstOperand operand = texOp.GetSource(0) as AstOperand; + + desc = new TextureDescriptor(samplerName, operand.CbufSlot, operand.CbufOffset); + } + else + { + desc = new TextureDescriptor(samplerName, texOp.Handle); + } + + context.TextureDescriptors.Add(desc); + } + } + + private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + string suffix = context.Config.Type == GalShaderType.Geometry ? "[]" : string.Empty; + + foreach (int attr in info.IAttributes.OrderBy(x => x)) + { + context.AppendLine($"layout (location = {attr}) in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};"); + } + } + + private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int attr in info.OAttributes.OrderBy(x => x)) + { + context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + } + + private static string GetSamplerTypeName(TextureType type) + { + string typeName; + + switch (type & TextureType.Mask) + { + case TextureType.Texture1D: typeName = "sampler1D"; break; + case TextureType.Texture2D: typeName = "sampler2D"; break; + case TextureType.Texture3D: typeName = "sampler3D"; break; + case TextureType.TextureCube: typeName = "samplerCube"; break; + + default: throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + + if ((type & TextureType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & TextureType.Array) != 0) + { + typeName += "Array"; + } + + if ((type & TextureType.Shadow) != 0) + { + typeName += "Shadow"; + } + + return typeName; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs new file mode 100644 index 000000000..1d3939fb7 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class DefaultNames + { + public const string LocalNamePrefix = "temp"; + + public const string SamplerNamePrefix = "tex"; + + public const string IAttributePrefix = "in_attr"; + public const string OAttributePrefix = "out_attr"; + + public const string UniformNamePrefix = "c"; + public const string UniformNameSuffix = "data"; + + public const string UndefinedName = "undef"; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs new file mode 100644 index 000000000..4edbda8b9 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs @@ -0,0 +1,133 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class GlslGenerator + { + public static GlslProgram Generate(StructuredProgramInfo info, ShaderConfig config) + { + CodeGenContext context = new CodeGenContext(config); + + Declarations.Declare(context, info); + + PrintMainBlock(context, info); + + return new GlslProgram( + context.CBufferDescriptors.ToArray(), + context.TextureDescriptors.ToArray(), + context.GetCode()); + } + + private static void PrintMainBlock(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine("void main()"); + + context.EnterScope(); + + Declarations.DeclareLocals(context, info); + + PrintBlock(context, info.MainBlock); + + context.LeaveScope(); + } + + private static void PrintBlock(CodeGenContext context, AstBlock block) + { + AstBlockVisitor visitor = new AstBlockVisitor(block); + + visitor.BlockEntered += (sender, e) => + { + switch (e.Block.Type) + { + case AstBlockType.DoWhile: + context.AppendLine("do"); + break; + + case AstBlockType.Else: + context.AppendLine("else"); + break; + + case AstBlockType.ElseIf: + context.AppendLine($"else if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + case AstBlockType.If: + context.AppendLine($"if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + default: throw new InvalidOperationException($"Found unexpected block type \"{e.Block.Type}\"."); + } + + context.EnterScope(); + }; + + visitor.BlockLeft += (sender, e) => + { + context.LeaveScope(); + + if (e.Block.Type == AstBlockType.DoWhile) + { + context.AppendLine($"while ({GetCondExpr(context, e.Block.Condition)});"); + } + }; + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstOperation operation) + { + if (operation.Inst == Instruction.Return) + { + PrepareForReturn(context); + } + + context.AppendLine(InstGen.GetExpression(context, operation) + ";"); + } + else if (node is AstAssignment assignment) + { + VariableType srcType = OperandManager.GetNodeDestType(assignment.Source); + VariableType dstType = OperandManager.GetNodeDestType(assignment.Destination); + + string dest; + + if (assignment.Destination is AstOperand operand && operand.Type == OperandType.Attribute) + { + dest = OperandManager.GetOutAttributeName(operand, context.Config.Type); + } + else + { + dest = InstGen.GetExpression(context, assignment.Destination); + } + + string src = ReinterpretCast(context, assignment.Source, srcType, dstType); + + context.AppendLine(dest + " = " + src + ";"); + } + else + { + throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + } + + private static string GetCondExpr(CodeGenContext context, IAstNode cond) + { + VariableType srcType = OperandManager.GetNodeDestType(cond); + + return ReinterpretCast(context, cond, srcType, VariableType.Bool); + } + + private static void PrepareForReturn(CodeGenContext context) + { + if (context.Config.Type == GalShaderType.Vertex) + { + context.AppendLine("gl_Position.xy *= flip;"); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs new file mode 100644 index 000000000..e616aa1f8 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class GlslProgram + { + public CBufferDescriptor[] CBufferDescriptors { get; } + public TextureDescriptor[] TextureDescriptors { get; } + + public string Code { get; } + + public GlslProgram( + CBufferDescriptor[] cBufferDescs, + TextureDescriptor[] textureDescs, + string code) + { + CBufferDescriptors = cBufferDescs; + TextureDescriptors = textureDescs; + Code = code; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs new file mode 100644 index 000000000..b0b2ec1a9 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -0,0 +1,110 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGen + { + public static string GetExpression(CodeGenContext context, IAstNode node) + { + if (node is AstOperation operation) + { + return GetExpression(context, operation); + } + else if (node is AstOperand operand) + { + return context.OperandManager.GetExpression(operand, context.Config.Type); + } + + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + + private static string GetExpression(CodeGenContext context, AstOperation operation) + { + Instruction inst = operation.Inst; + + InstInfo info = GetInstructionInfo(inst); + + if ((info.Type & InstType.Call) != 0) + { + int arity = (int)(info.Type & InstType.ArityMask); + + string args = string.Empty; + + for (int argIndex = 0; argIndex < arity; argIndex++) + { + if (argIndex != 0) + { + args += ", "; + } + + VariableType dstType = GetSrcVarType(inst, argIndex); + + args += GetSoureExpr(context, operation.GetSource(argIndex), dstType); + } + + return info.OpName + "(" + args + ")"; + } + else if ((info.Type & InstType.Op) != 0) + { + string op = info.OpName; + + int arity = (int)(info.Type & InstType.ArityMask); + + string[] expr = new string[arity]; + + for (int index = 0; index < arity; index++) + { + IAstNode src = operation.GetSource(index); + + string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index)); + + bool isLhs = arity == 2 && index == 0; + + expr[index] = Enclose(srcExpr, src, inst, info, isLhs); + } + + switch (arity) + { + case 0: + return op; + + case 1: + return op + expr[0]; + + case 2: + return $"{expr[0]} {op} {expr[1]}"; + + case 3: + return $"{expr[0]} {op[0]} {expr[1]} {op[1]} {expr[2]}"; + } + } + else if ((info.Type & InstType.Special) != 0) + { + switch (inst) + { + case Instruction.LoadConstant: + return InstGenMemory.LoadConstant(context, operation); + + case Instruction.PackHalf2x16: + return InstGenPacking.PackHalf2x16(context, operation); + + case Instruction.TextureSample: + return InstGenMemory.TextureSample(context, operation); + + case Instruction.TextureSize: + return InstGenMemory.TextureSize(context, operation); + + case Instruction.UnpackHalf2x16: + return InstGenPacking.UnpackHalf2x16(context, operation); + } + } + + throw new InvalidOperationException($"Unexpected instruction type \"{info.Type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs new file mode 100644 index 000000000..0b8600729 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -0,0 +1,170 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenHelper + { + private static InstInfo[] _infoTbl; + + static InstGenHelper() + { + _infoTbl = new InstInfo[(int)Instruction.Count]; + + Add(Instruction.Absolute, InstType.CallUnary, "abs"); + Add(Instruction.Add, InstType.OpBinaryCom, "+", 2); + Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "bitfieldInsert"); + Add(Instruction.BitfieldReverse, InstType.CallUnary, "bitfieldReverse"); + Add(Instruction.BitwiseAnd, InstType.OpBinaryCom, "&", 6); + Add(Instruction.BitwiseExclusiveOr, InstType.OpBinaryCom, "^", 7); + Add(Instruction.BitwiseNot, InstType.OpUnary, "~", 0); + Add(Instruction.BitwiseOr, InstType.OpBinaryCom, "|", 8); + Add(Instruction.Ceiling, InstType.CallUnary, "ceil"); + Add(Instruction.Clamp, InstType.CallTernary, "clamp"); + Add(Instruction.ClampU32, InstType.CallTernary, "clamp"); + Add(Instruction.CompareEqual, InstType.OpBinaryCom, "==", 5); + Add(Instruction.CompareGreater, InstType.OpBinary, ">", 4); + Add(Instruction.CompareGreaterOrEqual, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterOrEqualU32, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterU32, InstType.OpBinary, ">", 4); + Add(Instruction.CompareLess, InstType.OpBinary, "<", 4); + Add(Instruction.CompareLessOrEqual, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessOrEqualU32, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessU32, InstType.OpBinary, "<", 4); + Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5); + Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12); + Add(Instruction.ConvertFPToS32, InstType.CallUnary, "int"); + Add(Instruction.ConvertS32ToFP, InstType.CallUnary, "float"); + Add(Instruction.ConvertU32ToFP, InstType.CallUnary, "float"); + Add(Instruction.Cosine, InstType.CallUnary, "cos"); + Add(Instruction.Discard, InstType.OpNullary, "discard"); + Add(Instruction.Divide, InstType.OpBinary, "/", 1); + Add(Instruction.EmitVertex, InstType.CallNullary, "EmitVertex"); + Add(Instruction.EndPrimitive, InstType.CallNullary, "EndPrimitive"); + Add(Instruction.ExponentB2, InstType.CallUnary, "exp2"); + Add(Instruction.Floor, InstType.CallUnary, "floor"); + Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma"); + Add(Instruction.IsNan, InstType.CallUnary, "isnan"); + Add(Instruction.LoadConstant, InstType.Special); + Add(Instruction.LogarithmB2, InstType.CallUnary, "log2"); + Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9); + Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^^", 10); + Add(Instruction.LogicalNot, InstType.OpUnary, "!", 0); + Add(Instruction.LogicalOr, InstType.OpBinaryCom, "||", 11); + Add(Instruction.LoopBreak, InstType.OpNullary, "break"); + Add(Instruction.LoopContinue, InstType.OpNullary, "continue"); + Add(Instruction.PackHalf2x16, InstType.Special); + Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3); + Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3); + Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3); + Add(Instruction.Maximum, InstType.CallBinary, "max"); + Add(Instruction.MaximumU32, InstType.CallBinary, "max"); + Add(Instruction.Minimum, InstType.CallBinary, "min"); + Add(Instruction.MinimumU32, InstType.CallBinary, "min"); + Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1); + Add(Instruction.Negate, InstType.OpUnary, "-", 0); + Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "inversesqrt"); + Add(Instruction.Return, InstType.OpNullary, "return"); + Add(Instruction.Sine, InstType.CallUnary, "sin"); + Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt"); + Add(Instruction.Subtract, InstType.OpBinary, "-", 2); + Add(Instruction.TextureSample, InstType.Special); + Add(Instruction.TextureSize, InstType.Special); + Add(Instruction.Truncate, InstType.CallUnary, "trunc"); + Add(Instruction.UnpackHalf2x16, InstType.Special); + } + + private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0) + { + _infoTbl[(int)inst] = new InstInfo(flags, opName, precedence); + } + + public static InstInfo GetInstructionInfo(Instruction inst) + { + return _infoTbl[(int)(inst & Instruction.Mask)]; + } + + public static string GetSoureExpr(CodeGenContext context, IAstNode node, VariableType dstType) + { + return ReinterpretCast(context, node, OperandManager.GetNodeDestType(node), dstType); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, bool isLhs) + { + InstInfo pInfo = GetInstructionInfo(pInst); + + return Enclose(expr, node, pInst, pInfo, isLhs); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs = false) + { + if (NeedsParenthesis(node, pInst, pInfo, isLhs)) + { + expr = "(" + expr + ")"; + } + + return expr; + } + + public static bool NeedsParenthesis(IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs) + { + //If the node isn't a operation, then it can only be a operand, + //and those never needs to be surrounded in parenthesis. + if (!(node is AstOperation operation)) + { + //This is sort of a special case, if this is a negative constant, + //and it is consumed by a unary operation, we need to put on the parenthesis, + //as in GLSL a sequence like --2 or ~-1 is not valid. + if (IsNegativeConst(node) && pInfo.Type == InstType.OpUnary) + { + return true; + } + + return false; + } + + if ((pInfo.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + InstInfo info = _infoTbl[(int)(operation.Inst & Instruction.Mask)]; + + if ((info.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + if (info.Precedence < pInfo.Precedence) + { + return false; + } + + if (info.Precedence == pInfo.Precedence && isLhs) + { + return false; + } + + if (pInst == operation.Inst && info.Type == InstType.OpBinaryCom) + { + return false; + } + + return true; + } + + private static bool IsNegativeConst(IAstNode node) + { + if (!(node is AstOperand operand)) + { + return false; + } + + return operand.Type == OperandType.Constant && operand.Value < 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs new file mode 100644 index 000000000..79f80057f --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -0,0 +1,244 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenMemory + { + public static string LoadConstant(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + + offsetExpr = Enclose(offsetExpr, src1, Instruction.ShiftRightS32, isLhs: true); + + return OperandManager.GetConstantBufferName(operation.GetSource(0), offsetExpr, context.Config.Type); + } + + public static string TextureSample(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + bool isArray = (texOp.Type & TextureType.Array) != 0; + bool isMultisample = (texOp.Type & TextureType.Multisample) != 0; + bool isShadow = (texOp.Type & TextureType.Shadow) != 0; + + string texCall = intCoords ? "texelFetch" : "texture"; + + if (isGather) + { + texCall += "Gather"; + } + else if (hasLodLevel && !intCoords) + { + texCall += "Lod"; + } + + if (hasOffset) + { + texCall += "Offset"; + } + else if (hasOffsets) + { + texCall += "Offsets"; + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp); + + texCall += "(" + samplerName; + + int coordsCount = texOp.Type.GetCoordsCount(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + //The sampler 1D shadow overload expects a + //dummy value on the middle of the vector, who knows why... + bool hasDummy1DShadowElem = texOp.Type == (TextureType.Texture1D | TextureType.Shadow); + + if (hasDummy1DShadowElem) + { + pCount++; + } + + if (isShadow && !isGather) + { + pCount++; + } + + //On textureGather*, the comparison value is + //always specified as an extra argument. + bool hasExtraCompareArg = isShadow && isGather; + + if (pCount == 5) + { + pCount = 4; + + hasExtraCompareArg = true; + } + + int srcIndex = isBindless ? 1 : 0; + + string Src(VariableType type) + { + return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + } + + void Append(string str) + { + texCall += ", " + str; + } + + VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32; + + string AssemblePVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + if (arrayIndexElem == index) + { + elems[index] = Src(VariableType.S32); + + if (!intCoords) + { + elems[index] = "float(" + elems[index] + ")"; + } + } + else if (index == 1 && hasDummy1DShadowElem) + { + elems[index] = NumberFormatter.FormatFloat(0); + } + else + { + elems[index] = Src(coordType); + } + } + + string prefix = intCoords ? "i" : string.Empty; + + return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(coordType); + } + } + + Append(AssemblePVector(pCount)); + + if (hasExtraCompareArg) + { + Append(Src(VariableType.F32)); + } + + if (isMultisample) + { + Append(Src(VariableType.S32)); + } + else if (hasLodLevel) + { + Append(Src(coordType)); + } + + string AssembleOffsetVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(VariableType.S32); + } + + return "ivec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(VariableType.S32); + } + } + + if (hasOffset) + { + Append(AssembleOffsetVector(coordsCount)); + } + else if (hasOffsets) + { + texCall += $", ivec{coordsCount}[4]("; + + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ")"; + } + + if (hasLodBias) + { + Append(Src(VariableType.F32)); + } + + //textureGather* optional extra component index, + //not needed for shadow samplers. + if (isGather && !isShadow) + { + Append(Src(VariableType.S32)); + } + + texCall += ")" + (isGather || !isShadow ? GetMask(texOp.ComponentMask) : ""); + + return texCall; + } + + public static string TextureSize(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp); + + IAstNode src0 = operation.GetSource(isBindless ? 1 : 0); + + string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + + return $"textureSize({samplerName}, {src0Expr}){GetMask(texOp.ComponentMask)}"; + } + + private static string GetMask(int compMask) + { + string mask = "."; + + for (int index = 0; index < 4; index++) + { + if ((compMask & (1 << index)) != 0) + { + mask += "rgba".Substring(index, 1); + } + } + + return mask; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs new file mode 100644 index 000000000..4a40032c5 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs @@ -0,0 +1,45 @@ +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenPacking + { + public static string PackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src0 = operation.GetSource(0); + IAstNode src1 = operation.GetSource(1); + + string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + + return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; + } + + public static string UnpackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src = operation.GetSource(0); + + string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + + return $"unpackHalf2x16({srcExpr}){GetMask(operation.ComponentMask)}"; + } + + private static string GetMask(int compMask) + { + string mask = "."; + + for (int index = 0; index < 2; index++) + { + if ((compMask & (1 << index)) != 0) + { + mask += "xy".Substring(index, 1); + } + } + + return mask; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs new file mode 100644 index 000000000..fc9aef7ec --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + struct InstInfo + { + public InstType Type { get; } + + public string OpName { get; } + + public int Precedence { get; } + + public InstInfo(InstType type, string opName, int precedence) + { + Type = type; + OpName = opName; + Precedence = precedence; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs new file mode 100644 index 000000000..7d38a9d26 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + [Flags] + enum InstType + { + OpNullary = Op | 0, + OpUnary = Op | 1, + OpBinary = Op | 2, + OpTernary = Op | 3, + OpBinaryCom = OpBinary | Comutative, + + CallNullary = Call | 0, + CallUnary = Call | 1, + CallBinary = Call | 2, + CallTernary = Call | 3, + CallQuaternary = Call | 4, + + Comutative = 1 << 8, + Op = 1 << 9, + Call = 1 << 10, + Special = 1 << 11, + + ArityMask = 0xff + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs new file mode 100644 index 000000000..2ec44277c --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs @@ -0,0 +1,104 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Globalization; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class NumberFormatter + { + private const int MaxDecimal = 256; + + public static bool TryFormat(int value, VariableType dstType, out string formatted) + { + if (dstType == VariableType.F32) + { + return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted); + } + else if (dstType == VariableType.S32) + { + formatted = FormatInt(value); + } + else if (dstType == VariableType.U32) + { + formatted = FormatUint((uint)value); + } + else if (dstType == VariableType.Bool) + { + formatted = value != 0 ? "true" : "false"; + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + + return true; + } + + public static string FormatFloat(float value) + { + if (!TryFormatFloat(value, out string formatted)) + { + throw new ArgumentException("Failed to convert float value to string."); + } + + return formatted; + } + + public static bool TryFormatFloat(float value, out string formatted) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + formatted = null; + + return false; + } + + formatted = value.ToString("G9", CultureInfo.InvariantCulture); + + if (!(formatted.Contains('.') || + formatted.Contains('e') || + formatted.Contains('E'))) + { + formatted += ".0"; + } + + return true; + } + + public static string FormatInt(int value, VariableType dstType) + { + if (dstType == VariableType.S32) + { + return FormatInt(value); + } + else if (dstType == VariableType.U32) + { + return FormatUint((uint)value); + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + } + + public static string FormatInt(int value) + { + if (value <= MaxDecimal && value >= -MaxDecimal) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture); + } + + public static string FormatUint(uint value) + { + if (value <= MaxDecimal && value >= 0) + { + return value.ToString(CultureInfo.InvariantCulture) + "u"; + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture) + "u"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs new file mode 100644 index 000000000..debba4284 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs @@ -0,0 +1,239 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class OperandManager + { + private static string[] _stagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; + + private struct BuiltInAttribute + { + public string Name { get; } + + public VariableType Type { get; } + + public BuiltInAttribute(string name, VariableType type) + { + Name = name; + Type = type; + } + } + + private static Dictionary _builtInAttributes = + new Dictionary() + { + { AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", VariableType.S32) }, + { AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) }, + { AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) }, + { AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", VariableType.F32) }, + { AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", VariableType.F32) }, + { AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", VariableType.F32) }, + { AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", VariableType.F32) }, + { AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", VariableType.F32) }, + { AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", VariableType.F32) }, + { AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", VariableType.F32) }, + { AttributeConsts.InstanceId, new BuiltInAttribute("instance", VariableType.S32) }, + { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) }, + { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) }, + { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) } + }; + + private Dictionary _locals; + + public OperandManager() + { + _locals = new Dictionary(); + } + + public string DeclareLocal(AstOperand operand) + { + string name = $"{DefaultNames.LocalNamePrefix}_{_locals.Count}"; + + _locals.Add(operand, name); + + return name; + } + + public string GetExpression(AstOperand operand, GalShaderType shaderType) + { + switch (operand.Type) + { + case OperandType.Attribute: + return GetAttributeName(operand, shaderType); + + case OperandType.Constant: + return NumberFormatter.FormatInt(operand.Value); + + case OperandType.ConstantBuffer: + return GetConstantBufferName(operand, shaderType); + + case OperandType.LocalVariable: + return _locals[operand]; + + case OperandType.Undefined: + return DefaultNames.UndefinedName; + } + + throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."); + } + + public static string GetConstantBufferName(AstOperand cbuf, GalShaderType shaderType) + { + string ubName = GetUbName(shaderType, cbuf.CbufSlot); + + ubName += "[" + (cbuf.CbufOffset >> 2) + "]"; + + return ubName + "." + GetSwizzleMask(cbuf.CbufOffset & 3); + } + + public static string GetConstantBufferName(IAstNode slot, string offsetExpr, GalShaderType shaderType) + { + //Non-constant slots are not supported. + //It is expected that upstream stages are never going to generate non-constant + //slot access. + AstOperand operand = (AstOperand)slot; + + string ubName = GetUbName(shaderType, operand.Value); + + string index0 = "[" + offsetExpr + " >> 4]"; + string index1 = "[" + offsetExpr + " >> 2 & 3]"; + + return ubName + index0 + index1; + } + + public static string GetOutAttributeName(AstOperand attr, GalShaderType shaderType) + { + return GetAttributeName(attr, shaderType, isOutAttr: true); + } + + private static string GetAttributeName(AstOperand attr, GalShaderType shaderType, bool isOutAttr = false) + { + int value = attr.Value; + + string swzMask = GetSwizzleMask((value >> 2) & 3); + + if (value >= AttributeConsts.UserAttributeBase && + value < AttributeConsts.UserAttributeEnd) + { + value -= AttributeConsts.UserAttributeBase; + + string prefix = isOutAttr + ? DefaultNames.OAttributePrefix + : DefaultNames.IAttributePrefix; + + string name = $"{prefix}{(value >> 4)}"; + + if (shaderType == GalShaderType.Geometry && !isOutAttr) + { + name += "[0]"; + } + + name += "." + swzMask; + + return name; + } + else + { + if (value >= AttributeConsts.FragmentOutputColorBase && + value < AttributeConsts.FragmentOutputColorEnd) + { + value -= AttributeConsts.FragmentOutputColorBase; + + return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}"; + } + else if (_builtInAttributes.TryGetValue(value & ~3, out BuiltInAttribute builtInAttr)) + { + //TODO: There must be a better way to handle this... + if (shaderType == GalShaderType.Fragment) + { + switch (value & ~3) + { + case AttributeConsts.PositionX: return "gl_FragCoord.x"; + case AttributeConsts.PositionY: return "gl_FragCoord.y"; + case AttributeConsts.PositionZ: return "gl_FragCoord.z"; + case AttributeConsts.PositionW: return "1.0"; + } + } + + string name = builtInAttr.Name; + + if (shaderType == GalShaderType.Geometry && !isOutAttr) + { + name = "gl_in[0]." + name; + } + + return name; + } + } + + return DefaultNames.UndefinedName; + } + + public static string GetUbName(GalShaderType shaderType, int slot) + { + string ubName = OperandManager.GetShaderStagePrefix(shaderType); + + ubName += "_" + DefaultNames.UniformNamePrefix + slot; + + return ubName + "_" + DefaultNames.UniformNameSuffix; + } + + public static string GetSamplerName(GalShaderType shaderType, AstTextureOperation texOp) + { + string suffix; + + if ((texOp.Flags & TextureFlags.Bindless) != 0) + { + AstOperand operand = texOp.GetSource(0) as AstOperand; + + suffix = "_cb" + operand.CbufSlot + "_" + operand.CbufOffset; + } + else + { + suffix = (texOp.Handle - 8).ToString(); + } + + return GetShaderStagePrefix(shaderType) + "_" + DefaultNames.SamplerNamePrefix + suffix; + } + + public static string GetShaderStagePrefix(GalShaderType shaderType) + { + return _stagePrefixes[(int)shaderType]; + } + + private static string GetSwizzleMask(int value) + { + return "xyzw".Substring(value, 1); + } + + public static VariableType GetNodeDestType(IAstNode node) + { + if (node is AstOperation operation) + { + return GetDestVarType(operation.Inst); + } + else if (node is AstOperand operand) + { + if (operand.Type == OperandType.Attribute) + { + if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr)) + { + return builtInAttr.Type; + } + } + + return OperandInfo.GetVarType(operand); + } + else + { + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs new file mode 100644 index 000000000..7adc5ad33 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs @@ -0,0 +1,85 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class TypeConversion + { + public static string ReinterpretCast( + CodeGenContext context, + IAstNode node, + VariableType srcType, + VariableType dstType) + { + if (node is AstOperand operand && operand.Type == OperandType.Constant) + { + if (NumberFormatter.TryFormat(operand.Value, dstType, out string formatted)) + { + return formatted; + } + } + + string expr = InstGen.GetExpression(context, node); + + return ReinterpretCast(expr, node, srcType, dstType); + } + + private static string ReinterpretCast(string expr, IAstNode node, VariableType srcType, VariableType dstType) + { + if (srcType == dstType) + { + return expr; + } + + if (srcType == VariableType.F32) + { + switch (dstType) + { + case VariableType.S32: return $"floatBitsToInt({expr})"; + case VariableType.U32: return $"floatBitsToUint({expr})"; + } + } + else if (dstType == VariableType.F32) + { + switch (srcType) + { + case VariableType.Bool: return $"intBitsToFloat({ReinterpretBoolToInt(expr, node, VariableType.S32)})"; + case VariableType.S32: return $"intBitsToFloat({expr})"; + case VariableType.U32: return $"uintBitsToFloat({expr})"; + } + } + else if (srcType == VariableType.Bool) + { + return ReinterpretBoolToInt(expr, node, dstType); + } + else if (dstType == VariableType.Bool) + { + expr = InstGenHelper.Enclose(expr, node, Instruction.CompareNotEqual, isLhs: true); + + return $"({expr} != 0)"; + } + else if (dstType == VariableType.S32) + { + return $"int({expr})"; + } + else if (dstType == VariableType.U32) + { + return $"uint({expr})"; + } + + throw new ArgumentException($"Invalid reinterpret cast from \"{srcType}\" to \"{dstType}\"."); + } + + private static string ReinterpretBoolToInt(string expr, IAstNode node, VariableType dstType) + { + string trueExpr = NumberFormatter.FormatInt(IrConsts.True, dstType); + string falseExpr = NumberFormatter.FormatInt(IrConsts.False, dstType); + + expr = InstGenHelper.Enclose(expr, node, Instruction.ConditionalSelect, isLhs: false); + + return $"({expr} ? {trueExpr} : {falseExpr})"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs b/Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs new file mode 100644 index 000000000..3bb9bc1f4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class BitfieldExtensions + { + public static bool Extract(this int value, int lsb) + { + return ((int)(value >> lsb) & 1) != 0; + } + + public static int Extract(this int value, int lsb, int length) + { + return (int)(value >> lsb) & (int)(uint.MaxValue >> (32 - length)); + } + + public static bool Extract(this long value, int lsb) + { + return ((int)(value >> lsb) & 1) != 0; + } + + public static int Extract(this long value, int lsb, int length) + { + return (int)(value >> lsb) & (int)(uint.MaxValue >> (32 - length)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/Block.cs b/Ryujinx.Graphics/Shader/Decoders/Block.cs new file mode 100644 index 000000000..b5e610d71 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/Block.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class Block + { + public ulong Address { get; set; } + public ulong EndAddress { get; set; } + + public Block Next { get; set; } + public Block Branch { get; set; } + + public List OpCodes { get; } + public List SsyOpCodes { get; } + + public Block(ulong address) + { + Address = address; + + OpCodes = new List(); + SsyOpCodes = new List(); + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if (OpCodes[splitIndex].Address < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + + rightBlock.Next = Next; + rightBlock.Branch = Branch; + + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + rightBlock.UpdateSsyOpCodes(); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + + UpdateSsyOpCodes(); + } + + private static int BinarySearch(List opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + OpCode opCode = opCodes[middle]; + + if (address == opCode.Address) + { + break; + } + + if (address < opCode.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + + public OpCode GetLastOp() + { + if (OpCodes.Count != 0) + { + return OpCodes[OpCodes.Count - 1]; + } + + return null; + } + + public void UpdateSsyOpCodes() + { + SsyOpCodes.Clear(); + + for (int index = 0; index < OpCodes.Count; index++) + { + if (!(OpCodes[index] is OpCodeSsy op)) + { + continue; + } + + SsyOpCodes.Add(op); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/Condition.cs b/Ryujinx.Graphics/Shader/Decoders/Condition.cs new file mode 100644 index 000000000..10400f94a --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/Condition.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum Condition + { + Less = 1 << 0, + Equal = 1 << 1, + Greater = 1 << 2, + Nan = 1 << 3, + Unsigned = 1 << 4, + + Never = 0, + + LessOrEqual = Less | Equal, + NotEqual = Less | Greater, + GreaterOrEqual = Greater | Equal, + Number = Greater | Equal | Less, + + LessUnordered = Less | Nan, + EqualUnordered = Equal | Nan, + LessOrEqualUnordered = LessOrEqual | Nan, + GreaterUnordered = Greater | Nan, + NotEqualUnordered = NotEqual | Nan, + GreaterOrEqualUnordered = GreaterOrEqual | Nan, + + Always = 0xf, + + Off = Unsigned | Never, + Lower = Unsigned | Less, + Sff = Unsigned | Equal, + LowerOrSame = Unsigned | LessOrEqual, + Higher = Unsigned | Greater, + Sft = Unsigned | NotEqual, + HigherOrSame = Unsigned | GreaterOrEqual, + Oft = Unsigned | Always, + + CsmTa = 0x18, + CsmTr = 0x19, + CsmMx = 0x1a, + FcsmTa = 0x1b, + FcsmTr = 0x1c, + FcsmMx = 0x1d, + Rle = 0x1e, + Rgt = 0x1f + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/ConditionalOperation.cs b/Ryujinx.Graphics/Shader/Decoders/ConditionalOperation.cs new file mode 100644 index 000000000..4fc31e842 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/ConditionalOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ConditionalOperation + { + False = 0, + True = 1, + Zero = 2, + NotZero = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/Decoder.cs b/Ryujinx.Graphics/Shader/Decoders/Decoder.cs new file mode 100644 index 000000000..86df3b203 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/Decoder.cs @@ -0,0 +1,406 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.Instructions; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class Decoder + { + private const long HeaderSize = 0x50; + + private delegate object OpActivator(InstEmitter emitter, ulong address, long opCode); + + private static ConcurrentDictionary _opActivators; + + static Decoder() + { + _opActivators = new ConcurrentDictionary(); + } + + public static Block[] Decode(IGalMemory memory, ulong address) + { + List blocks = new List(); + + Queue workQueue = new Queue(); + + Dictionary visited = new Dictionary(); + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + block = new Block(blkAddress); + + workQueue.Enqueue(block); + + visited.Add(blkAddress, block); + } + + return block; + } + + ulong startAddress = address + HeaderSize; + + GetBlock(startAddress); + + while (workQueue.TryDequeue(out Block currBlock)) + { + //Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) + { + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) + { + throw new InvalidOperationException("Found duplicate block address on the list."); + } + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + //If we have a block after the current one, set the limit address. + ulong limitAddress = ulong.MaxValue; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + FillBlock(memory, currBlock, limitAddress, startAddress); + + if (currBlock.OpCodes.Count != 0) + { + foreach (OpCodeSsy ssyOp in currBlock.SsyOpCodes) + { + GetBlock(ssyOp.GetAbsoluteAddress()); + } + + //Set child blocks. "Branch" is the block the branch instruction + //points to (when taken), "Next" is the block at the next address, + //executed when the branch is not taken. For Unconditional Branches + //or end of program, Next is null. + OpCode lastOp = currBlock.GetLastOp(); + + if (lastOp is OpCodeBranch op) + { + currBlock.Branch = GetBlock(op.GetAbsoluteAddress()); + } + + if (!IsUnconditionalBranch(lastOp)) + { + currBlock.Next = GetBlock(currBlock.EndAddress); + } + } + + //Insert the new block on the list (sorted by address). + if (blocks.Count != 0) + { + Block nBlock = blocks[nBlkIndex]; + + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); + } + } + + foreach (Block ssyBlock in blocks.Where(x => x.SsyOpCodes.Count != 0)) + { + for (int ssyIndex = 0; ssyIndex < ssyBlock.SsyOpCodes.Count; ssyIndex++) + { + PropagateSsy(visited, ssyBlock, ssyIndex); + } + } + + return blocks.ToArray(); + } + + private static bool BinarySearch(List blocks, ulong address, out int index) + { + index = 0; + + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Block block = blocks[middle]; + + index = middle; + + if (address >= block.Address && address < block.EndAddress) + { + return true; + } + + if (address < block.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return false; + } + + private static void FillBlock( + IGalMemory memory, + Block block, + ulong limitAddress, + ulong startAddress) + { + ulong address = block.Address; + + do + { + if (address >= limitAddress) + { + break; + } + + //Ignore scheduling instructions, which are written every 32 bytes. + if (((address - startAddress) & 0x1f) == 0) + { + address += 8; + + continue; + } + + uint word0 = (uint)memory.ReadInt32((long)(address + 0)); + uint word1 = (uint)memory.ReadInt32((long)(address + 4)); + + ulong opAddress = address; + + address += 8; + + long opCode = word0 | (long)word1 << 32; + + (InstEmitter emitter, Type opCodeType) = OpCodeTable.GetEmitter(opCode); + + if (emitter == null) + { + //TODO: Warning, illegal encoding. + continue; + } + + OpCode op = MakeOpCode(opCodeType, emitter, opAddress, opCode); + + block.OpCodes.Add(op); + } + while (!IsBranch(block.GetLastOp())); + + block.EndAddress = address; + + block.UpdateSsyOpCodes(); + } + + private static bool IsUnconditionalBranch(OpCode opCode) + { + return IsUnconditional(opCode) && IsBranch(opCode); + } + + private static bool IsUnconditional(OpCode opCode) + { + if (opCode is OpCodeExit op && op.Condition != Condition.Always) + { + return false; + } + + return opCode.Predicate.Index == RegisterConsts.PredicateTrueIndex && !opCode.InvertPredicate; + } + + private static bool IsBranch(OpCode opCode) + { + return (opCode is OpCodeBranch && opCode.Emitter != InstEmit.Ssy) || + opCode is OpCodeSync || + opCode is OpCodeExit; + } + + private static OpCode MakeOpCode(Type type, InstEmitter emitter, ulong address, long opCode) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + OpActivator createInstance = _opActivators.GetOrAdd(type, CacheOpActivator); + + return (OpCode)createInstance(emitter, address, opCode); + } + + private static OpActivator CacheOpActivator(Type type) + { + Type[] argTypes = new Type[] { typeof(InstEmitter), typeof(ulong), typeof(long) }; + + DynamicMethod mthd = new DynamicMethod($"Make{type.Name}", type, argTypes); + + ILGenerator generator = mthd.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Newobj, type.GetConstructor(argTypes)); + generator.Emit(OpCodes.Ret); + + return (OpActivator)mthd.CreateDelegate(typeof(OpActivator)); + } + + private struct PathBlockState + { + public Block Block { get; } + + private enum RestoreType + { + None, + PopSsy, + PushSync + } + + private RestoreType _restoreType; + + private ulong _restoreValue; + + public bool ReturningFromVisit => _restoreType != RestoreType.None; + + public PathBlockState(Block block) + { + Block = block; + _restoreType = RestoreType.None; + _restoreValue = 0; + } + + public PathBlockState(int oldSsyStackSize) + { + Block = null; + _restoreType = RestoreType.PopSsy; + _restoreValue = (ulong)oldSsyStackSize; + } + + public PathBlockState(ulong syncAddress) + { + Block = null; + _restoreType = RestoreType.PushSync; + _restoreValue = syncAddress; + } + + public void RestoreStackState(Stack ssyStack) + { + if (_restoreType == RestoreType.PushSync) + { + ssyStack.Push(_restoreValue); + } + else if (_restoreType == RestoreType.PopSsy) + { + while (ssyStack.Count > (uint)_restoreValue) + { + ssyStack.Pop(); + } + } + } + } + + private static void PropagateSsy(Dictionary blocks, Block ssyBlock, int ssyIndex) + { + OpCodeSsy ssyOp = ssyBlock.SsyOpCodes[ssyIndex]; + + Stack workQueue = new Stack(); + + HashSet visited = new HashSet(); + + Stack ssyStack = new Stack(); + + void Push(PathBlockState pbs) + { + if (pbs.Block == null || visited.Add(pbs.Block)) + { + workQueue.Push(pbs); + } + } + + Push(new PathBlockState(ssyBlock)); + + while (workQueue.TryPop(out PathBlockState pbs)) + { + if (pbs.ReturningFromVisit) + { + pbs.RestoreStackState(ssyStack); + + continue; + } + + Block current = pbs.Block; + + int ssyOpCodesCount = current.SsyOpCodes.Count; + + if (ssyOpCodesCount != 0) + { + Push(new PathBlockState(ssyStack.Count)); + + for (int index = ssyIndex; index < ssyOpCodesCount; index++) + { + ssyStack.Push(current.SsyOpCodes[index].GetAbsoluteAddress()); + } + } + + ssyIndex = 0; + + if (current.Next != null) + { + Push(new PathBlockState(current.Next)); + } + + if (current.Branch != null) + { + Push(new PathBlockState(current.Branch)); + } + else if (current.GetLastOp() is OpCodeSync op) + { + ulong syncAddress = ssyStack.Pop(); + + if (ssyStack.Count == 0) + { + ssyStack.Push(syncAddress); + + op.Targets.Add(ssyOp, op.Targets.Count); + + ssyOp.Syncs.TryAdd(op, Local()); + } + else + { + Push(new PathBlockState(syncAddress)); + Push(new PathBlockState(blocks[syncAddress])); + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs b/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs new file mode 100644 index 000000000..fd0a45e82 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs @@ -0,0 +1,58 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class DecoderHelper + { + public static int DecodeS20Immediate(long opCode) + { + int imm = opCode.Extract(20, 19); + + bool negate = opCode.Extract(56); + + if (negate) + { + imm = -imm; + } + + return imm; + } + + public static int Decode2xF10Immediate(long opCode) + { + int immH0 = opCode.Extract(20, 9); + int immH1 = opCode.Extract(30, 9); + + bool negateH0 = opCode.Extract(29); + bool negateH1 = opCode.Extract(56); + + if (negateH0) + { + immH0 |= 1 << 9; + } + + if (negateH1) + { + immH1 |= 1 << 9; + } + + return immH1 << 22 | immH0 << 6; + } + + public static float DecodeF20Immediate(long opCode) + { + int imm = opCode.Extract(20, 19); + + bool negate = opCode.Extract(56); + + imm <<= 12; + + if (negate) + { + imm |= 1 << 31; + } + + return BitConverter.Int32BitsToSingle(imm); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs b/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs new file mode 100644 index 000000000..3ddf17cfc --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPHalfSwizzle + { + FP16 = 0, + FP32 = 1, + DupH0 = 2, + DupH1 = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/FPType.cs b/Ryujinx.Graphics/Shader/Decoders/FPType.cs new file mode 100644 index 000000000..e602ad45f --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/FPType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPType + { + FP16 = 1, + FP32 = 2, + FP64 = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/FmulScale.cs b/Ryujinx.Graphics/Shader/Decoders/FmulScale.cs new file mode 100644 index 000000000..c35c6e489 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/FmulScale.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FmulScale + { + None = 0, + Divide2 = 1, + Divide4 = 2, + Divide8 = 3, + Multiply8 = 4, + Multiply4 = 5, + Multiply2 = 6 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCode.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCode.cs new file mode 100644 index 000000000..dd6ad79a2 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCode.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCode + { + InstEmitter Emitter { get; } + + ulong Address { get; } + long RawOpCode { get; } + + Register Predicate { get; } + + bool InvertPredicate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs new file mode 100644 index 000000000..d840d49d1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeAlu : IOpCodeRd, IOpCodeRa + { + Register Predicate39 { get; } + + bool InvertP { get; } + bool Extended { get; } + bool SetCondCode { get; } + bool Saturate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeCbuf.cs new file mode 100644 index 000000000..42a174514 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeCbuf.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeCbuf : IOpCode + { + int Offset { get; } + int Slot { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs new file mode 100644 index 000000000..d68ccf593 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeFArith : IOpCodeAlu + { + RoundingMode RoundingMode { get; } + + FmulScale Scale { get; } + + bool FlushToZero { get; } + bool AbsoluteA { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs new file mode 100644 index 000000000..4638f6608 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeHfma : IOpCode + { + bool NegateB { get; } + bool NegateC { get; } + bool Saturate { get; } + + FPHalfSwizzle SwizzleA { get; } + FPHalfSwizzle SwizzleB { get; } + FPHalfSwizzle SwizzleC { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs new file mode 100644 index 000000000..9cfcd69b0 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeImm : IOpCode + { + int Immediate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeImmF.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeImmF.cs new file mode 100644 index 000000000..629eff797 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeImmF.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeImmF : IOpCode + { + float Immediate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeLop.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeLop.cs new file mode 100644 index 000000000..62c87bf43 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeLop.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeLop : IOpCodeAlu + { + LogicalOperation LogicalOp { get; } + + bool InvertA { get; } + bool InvertB { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs new file mode 100644 index 000000000..e5902110e --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRa : IOpCode + { + Register Ra { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs new file mode 100644 index 000000000..bb806b95c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRc : IOpCode + { + Register Rc { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs new file mode 100644 index 000000000..099c4061a --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRd : IOpCode + { + Register Rd { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeReg.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeReg.cs new file mode 100644 index 000000000..3ed157e82 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeReg.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeReg : IOpCode + { + Register Rb { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs new file mode 100644 index 000000000..429f01bb7 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRegCbuf : IOpCodeRc + { + int Offset { get; } + int Slot { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs new file mode 100644 index 000000000..a1937c2f5 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerCondition + { + Less = 1 << 0, + Equal = 1 << 1, + Greater = 1 << 2, + + Never = 0, + + LessOrEqual = Less | Equal, + NotEqual = Less | Greater, + GreaterOrEqual = Greater | Equal, + Number = Greater | Equal | Less, + + Always = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs new file mode 100644 index 000000000..b779f44d4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerHalfPart + { + B32 = 0, + H0 = 1, + H1 = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs new file mode 100644 index 000000000..ce4d9f3bb --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerShift + { + NoShift = 0, + ShiftRight = 1, + ShiftLeft = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs new file mode 100644 index 000000000..70fdfc3dc --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerSize + { + U8 = 0, + S8 = 1, + U16 = 2, + S16 = 3, + B32 = 4, + B64 = 5 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerType.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerType.cs new file mode 100644 index 000000000..46734dbe4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IntegerType.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerType + { + U8 = 0, + U16 = 1, + U32 = 2, + U64 = 3, + S8 = 4, + S16 = 5, + S32 = 6, + S64 = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs b/Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs new file mode 100644 index 000000000..522144251 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum LogicalOperation + { + And = 0, + Or = 1, + ExclusiveOr = 2, + Passthrough = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs b/Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs new file mode 100644 index 000000000..88bd1f5ce --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum MufuOperation + { + Cosine = 0, + Sine = 1, + ExponentB2 = 2, + LogarithmB2 = 3, + Reciprocal = 4, + ReciprocalSquareRoot = 5, + Reciprocal64H = 6, + ReciprocalSquareRoot64H = 7, + SquareRoot = 8 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCode.cs b/Ryujinx.Graphics/Shader/Decoders/OpCode.cs new file mode 100644 index 000000000..b0f2ffdc3 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCode.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCode + { + public InstEmitter Emitter { get; } + + public ulong Address { get; } + public long RawOpCode { get; } + + public Register Predicate { get; protected set; } + + public bool InvertPredicate { get; protected set; } + + //When inverted, the always true predicate == always false. + public bool NeverExecute => Predicate.Index == RegisterConsts.PredicateTrueIndex && InvertPredicate; + + public OpCode(InstEmitter emitter, ulong address, long opCode) + { + Emitter = emitter; + Address = address; + RawOpCode = opCode; + + Predicate = new Register(opCode.Extract(16, 3), RegisterType.Predicate); + + InvertPredicate = opCode.Extract(19); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs new file mode 100644 index 000000000..15fbb9af7 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs @@ -0,0 +1,34 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAlu : OpCode, IOpCodeAlu, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; } + public Register Predicate39 { get; } + + public int ByteSelection { get; } + + public bool InvertP { get; } + public bool Extended { get; protected set; } + public bool SetCondCode { get; protected set; } + public bool Saturate { get; protected set; } + + public OpCodeAlu(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + Predicate39 = new Register(opCode.Extract(39, 3), RegisterType.Predicate); + + ByteSelection = opCode.Extract(41, 2); + + InvertP = opCode.Extract(42); + Extended = opCode.Extract(43); + SetCondCode = opCode.Extract(47); + Saturate = opCode.Extract(50); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluCbuf.cs new file mode 100644 index 000000000..9c1279894 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluCbuf : OpCodeAlu, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeAluCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs new file mode 100644 index 000000000..a407fc6bf --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs new file mode 100644 index 000000000..9aeb32bd4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm2x10 : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs new file mode 100644 index 000000000..5941e0b9a --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm32 : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = opCode.Extract(20, 32); + + SetCondCode = opCode.Extract(52); + Extended = opCode.Extract(53); + Saturate = opCode.Extract(54); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluReg.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluReg.cs new file mode 100644 index 000000000..13b96a3ae --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluReg : OpCodeAlu, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeAluReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluRegCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluRegCbuf.cs new file mode 100644 index 000000000..6cf6bd2e2 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluRegCbuf.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluRegCbuf : OpCodeAluReg, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeAluRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + Rb = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAttribute.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAttribute.cs new file mode 100644 index 000000000..fd8e63fcf --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAttribute.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAttribute : OpCodeAluReg + { + public int AttributeOffset { get; } + public int Count { get; } + + public OpCodeAttribute(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + AttributeOffset = opCode.Extract(20, 10); + Count = opCode.Extract(47, 2) + 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs new file mode 100644 index 000000000..25941b396 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBranch : OpCode + { + public int Offset { get; } + + public OpCodeBranch(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = ((int)(opCode >> 20) << 8) >> 8; + } + + public ulong GetAbsoluteAddress() + { + return (ulong)((long)Address + (long)Offset + 8); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs new file mode 100644 index 000000000..d50903eb4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeExit : OpCode + { + public Condition Condition { get; } + + public OpCodeExit(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Condition = (Condition)opCode.Extract(0, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs new file mode 100644 index 000000000..c88f7f0ee --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArith : OpCodeAlu, IOpCodeFArith + { + public RoundingMode RoundingMode { get; } + + public FmulScale Scale { get; } + + public bool FlushToZero { get; } + public bool AbsoluteA { get; } + + public OpCodeFArith(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + RoundingMode = (RoundingMode)opCode.Extract(39, 2); + + Scale = (FmulScale)opCode.Extract(41, 3); + + FlushToZero = opCode.Extract(44); + AbsoluteA = opCode.Extract(46); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithCbuf.cs new file mode 100644 index 000000000..5486bb0b0 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithCbuf : OpCodeFArith, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeFArithCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm.cs new file mode 100644 index 000000000..1bb6f4255 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithImm : OpCodeFArith, IOpCodeImmF + { + public float Immediate { get; } + + public OpCodeFArithImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeF20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs new file mode 100644 index 000000000..ec9da6f30 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithImm32 : OpCodeAlu, IOpCodeFArith, IOpCodeImmF + { + public RoundingMode RoundingMode => RoundingMode.ToNearest; + + public FmulScale Scale => FmulScale.None; + + public bool FlushToZero { get; } + public bool AbsoluteA { get; } + + public float Immediate { get; } + + public OpCodeFArithImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + int imm = opCode.Extract(20, 32); + + Immediate = BitConverter.Int32BitsToSingle(imm); + + SetCondCode = opCode.Extract(52); + AbsoluteA = opCode.Extract(54); + FlushToZero = opCode.Extract(55); + + Saturate = false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithReg.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithReg.cs new file mode 100644 index 000000000..55cf44859 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithReg : OpCodeFArith, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeFArithReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs new file mode 100644 index 000000000..315c2c8b1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithRegCbuf : OpCodeFArith, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeFArithRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs new file mode 100644 index 000000000..cb5f155e8 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFsetImm : OpCodeSet, IOpCodeImmF + { + public float Immediate { get; } + + public OpCodeFsetImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeF20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs new file mode 100644 index 000000000..32f3cd7ab --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfma : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; protected set; } + + public FPHalfSwizzle SwizzleA { get; } + + public OpCodeHfma(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + SwizzleA = (FPHalfSwizzle)opCode.Extract(47, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs new file mode 100644 index 000000000..33768c7d0 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP32; + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2); + + NegateB = opCode.Extract(56); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs new file mode 100644 index 000000000..80a5a1408 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaImm2x10 : OpCodeHfma, IOpCodeHfma, IOpCodeImm + { + public int Immediate { get; } + + public bool NegateB => false; + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16; + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs new file mode 100644 index 000000000..05eb9ffe0 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaImm32 : OpCodeHfma, IOpCodeHfma, IOpCodeImm + { + public int Immediate { get; } + + public bool NegateB => false; + public bool NegateC { get; } + public bool Saturate => false; + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16; + public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP16; + + public OpCodeHfmaImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = opCode.Extract(20, 32); + + NegateC = opCode.Extract(52); + + Rc = Rd; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs new file mode 100644 index 000000000..714c89dea --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaReg : OpCodeHfma, IOpCodeHfma, IOpCodeReg + { + public Register Rb { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB { get; } + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + SwizzleB = (FPHalfSwizzle)opCode.Extract(28, 2); + + NegateC = opCode.Extract(30); + NegateB = opCode.Extract(31); + Saturate = opCode.Extract(32); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(35, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs new file mode 100644 index 000000000..c0001908c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaRegCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB { get; } + public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP32; + + public OpCodeHfmaRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleB = (FPHalfSwizzle)opCode.Extract(53, 2); + + NegateB = opCode.Extract(56); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs new file mode 100644 index 000000000..e21095a32 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeIpa : OpCodeAluReg + { + public int AttributeOffset { get; } + + public OpCodeIpa(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + AttributeOffset = opCode.Extract(28, 10); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs new file mode 100644 index 000000000..cc9f06582 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLdc : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeCbuf + { + public Register Rd { get; } + public Register Ra { get; } + + public int Offset { get; } + public int Slot { get; } + + public IntegerSize Size { get; } + + public OpCodeLdc(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = opCode.Extract(22, 14); + Slot = opCode.Extract(36, 5); + + Size = (IntegerSize)opCode.Extract(48, 3); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs new file mode 100644 index 000000000..c5f903451 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLop : OpCodeAlu, IOpCodeLop + { + public bool InvertA { get; protected set; } + public bool InvertB { get; protected set; } + + public LogicalOperation LogicalOp { get; } + + public ConditionalOperation CondOp { get; } + + public Register Predicate48 { get; } + + public OpCodeLop(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + InvertA = opCode.Extract(39); + InvertB = opCode.Extract(40); + + LogicalOp = (LogicalOperation)opCode.Extract(41, 2); + + CondOp = (ConditionalOperation)opCode.Extract(44, 2); + + Predicate48 = new Register(opCode.Extract(48, 3), RegisterType.Predicate); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopCbuf.cs new file mode 100644 index 000000000..f174733c4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopCbuf : OpCodeLop, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeLopCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm.cs new file mode 100644 index 000000000..a2f091a2c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopImm : OpCodeLop, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeLopImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm32.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm32.cs new file mode 100644 index 000000000..cb48f3a61 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm32.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopImm32 : OpCodeAluImm32, IOpCodeLop, IOpCodeImm + { + public LogicalOperation LogicalOp { get; } + + public bool InvertA { get; } + public bool InvertB { get; } + + public OpCodeLopImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + LogicalOp = (LogicalOperation)opCode.Extract(53, 2); + + InvertA = opCode.Extract(55); + InvertB = opCode.Extract(56); + + Extended = opCode.Extract(57); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopReg.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopReg.cs new file mode 100644 index 000000000..5f43db72b --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeLopReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopReg : OpCodeLop, IOpCodeReg + { + public Register Rb { get; } + + public OpCodeLopReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs new file mode 100644 index 000000000..729e3207e --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodePsetp : OpCodeSet + { + public Register Predicate12 { get; } + public Register Predicate29 { get; } + + public LogicalOperation LogicalOpAB { get; } + + public OpCodePsetp(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Predicate12 = new Register(opCode.Extract(12, 3), RegisterType.Predicate); + Predicate29 = new Register(opCode.Extract(29, 3), RegisterType.Predicate); + + LogicalOpAB = (LogicalOperation)opCode.Extract(24, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs new file mode 100644 index 000000000..cd6773a13 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSet : OpCodeAlu + { + public Register Predicate0 { get; } + public Register Predicate3 { get; } + + public bool NegateP { get; } + + public LogicalOperation LogicalOp { get; } + + public bool FlushToZero { get; } + + public OpCodeSet(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Predicate0 = new Register(opCode.Extract(0, 3), RegisterType.Predicate); + Predicate3 = new Register(opCode.Extract(3, 3), RegisterType.Predicate); + + LogicalOp = (LogicalOperation)opCode.Extract(45, 2); + + FlushToZero = opCode.Extract(47); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSetCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeSetCbuf.cs new file mode 100644 index 000000000..4f3dbd741 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeSetCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetCbuf : OpCodeSet, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeSetCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSetImm.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeSetImm.cs new file mode 100644 index 000000000..bc63b9f47 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeSetImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetImm : OpCodeSet, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeSetImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSetReg.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeSetReg.cs new file mode 100644 index 000000000..bbdee1962 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeSetReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetReg : OpCodeSet, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeSetReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs new file mode 100644 index 000000000..499c07068 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSsy : OpCodeBranch + { + public Dictionary Syncs { get; } + + public OpCodeSsy(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Syncs = new Dictionary(); + + Predicate = new Register(RegisterConsts.PredicateTrueIndex, RegisterType.Predicate); + + InvertPredicate = false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs new file mode 100644 index 000000000..081d08a0d --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs @@ -0,0 +1,15 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSync : OpCode + { + public Dictionary Targets { get; } + + public OpCodeSync(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Targets = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs new file mode 100644 index 000000000..d588ce8ee --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs @@ -0,0 +1,216 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class OpCodeTable + { + private const int EncodingBits = 14; + + private class TableEntry + { + public InstEmitter Emitter { get; } + + public Type OpCodeType { get; } + + public int XBits { get; } + + public TableEntry(InstEmitter emitter, Type opCodeType, int xBits) + { + Emitter = emitter; + OpCodeType = opCodeType; + XBits = xBits; + } + } + + private static TableEntry[] _opCodes; + + static OpCodeTable() + { + _opCodes = new TableEntry[1 << EncodingBits]; + +#region Instructions + Set("1110111111011x", InstEmit.Ald, typeof(OpCodeAttribute)); + Set("1110111111110x", InstEmit.Ast, typeof(OpCodeAttribute)); + Set("0100110000000x", InstEmit.Bfe, typeof(OpCodeAluCbuf)); + Set("0011100x00000x", InstEmit.Bfe, typeof(OpCodeAluImm)); + Set("0101110000000x", InstEmit.Bfe, typeof(OpCodeAluReg)); + Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch)); + Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit)); + Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf)); + Set("0011100x10101x", InstEmit.F2F, typeof(OpCodeFArithImm)); + Set("0101110010101x", InstEmit.F2F, typeof(OpCodeFArithReg)); + Set("0100110010110x", InstEmit.F2I, typeof(OpCodeFArithCbuf)); + Set("0011100x10110x", InstEmit.F2I, typeof(OpCodeFArithImm)); + Set("0101110010110x", InstEmit.F2I, typeof(OpCodeFArithReg)); + Set("0100110001011x", InstEmit.Fadd, typeof(OpCodeFArithCbuf)); + Set("0011100x01011x", InstEmit.Fadd, typeof(OpCodeFArithImm)); + Set("000010xxxxxxxx", InstEmit.Fadd, typeof(OpCodeFArithImm32)); + Set("0101110001011x", InstEmit.Fadd, typeof(OpCodeFArithReg)); + Set("010010011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithCbuf)); + Set("0011001x1xxxxx", InstEmit.Ffma, typeof(OpCodeFArithImm)); + Set("010100011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithRegCbuf)); + Set("010110011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithReg)); + Set("0100110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithCbuf)); + Set("0011100x01100x", InstEmit.Fmnmx, typeof(OpCodeFArithImm)); + Set("0101110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithReg)); + Set("0100110001101x", InstEmit.Fmul, typeof(OpCodeFArithCbuf)); + Set("0011100x01101x", InstEmit.Fmul, typeof(OpCodeFArithImm)); + Set("00011110xxxxxx", InstEmit.Fmul, typeof(OpCodeFArithImm32)); + Set("0101110001101x", InstEmit.Fmul, typeof(OpCodeFArithReg)); + Set("0100100xxxxxxx", InstEmit.Fset, typeof(OpCodeSetCbuf)); + Set("0011000xxxxxxx", InstEmit.Fset, typeof(OpCodeFsetImm)); + Set("01011000xxxxxx", InstEmit.Fset, typeof(OpCodeSetReg)); + Set("010010111011xx", InstEmit.Fsetp, typeof(OpCodeSetCbuf)); + Set("0011011x1011xx", InstEmit.Fsetp, typeof(OpCodeFsetImm)); + Set("010110111011xx", InstEmit.Fsetp, typeof(OpCodeSetReg)); + Set("0111101x1xxxxx", InstEmit.Hadd2, typeof(OpCodeAluCbuf)); + Set("0111101x0xxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm2x10)); + Set("0010110xxxxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm32)); + Set("0101110100010x", InstEmit.Hadd2, typeof(OpCodeAluReg)); + Set("01110xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaCbuf)); + Set("01110xxx0xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm2x10)); + Set("0010100xxxxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm32)); + Set("0101110100000x", InstEmit.Hfma2, typeof(OpCodeHfmaReg)); + Set("01100xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaRegCbuf)); + Set("0111100x1xxxxx", InstEmit.Hmul2, typeof(OpCodeAluCbuf)); + Set("0111100x0xxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm2x10)); + Set("0010101xxxxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm32)); + Set("0101110100001x", InstEmit.Hmul2, typeof(OpCodeAluReg)); + Set("0100110010111x", InstEmit.I2F, typeof(OpCodeAluCbuf)); + Set("0011100x10111x", InstEmit.I2F, typeof(OpCodeAluImm)); + Set("0101110010111x", InstEmit.I2F, typeof(OpCodeAluReg)); + Set("0100110011100x", InstEmit.I2I, typeof(OpCodeAluCbuf)); + Set("0011100x11100x", InstEmit.I2I, typeof(OpCodeAluImm)); + Set("0101110011100x", InstEmit.I2I, typeof(OpCodeAluReg)); + Set("0100110000010x", InstEmit.Iadd, typeof(OpCodeAluCbuf)); + Set("0011100000010x", InstEmit.Iadd, typeof(OpCodeAluImm)); + Set("0001110x0xxxxx", InstEmit.Iadd, typeof(OpCodeAluImm32)); + Set("0101110000010x", InstEmit.Iadd, typeof(OpCodeAluReg)); + Set("010011001100xx", InstEmit.Iadd3, typeof(OpCodeAluCbuf)); + Set("001110001100xx", InstEmit.Iadd3, typeof(OpCodeAluImm)); + Set("010111001100xx", InstEmit.Iadd3, typeof(OpCodeAluReg)); + Set("0100110000100x", InstEmit.Imnmx, typeof(OpCodeAluCbuf)); + Set("0011100x00100x", InstEmit.Imnmx, typeof(OpCodeAluImm)); + Set("0101110000100x", InstEmit.Imnmx, typeof(OpCodeAluReg)); + Set("11100000xxxxxx", InstEmit.Ipa, typeof(OpCodeIpa)); + Set("0100110000011x", InstEmit.Iscadd, typeof(OpCodeAluCbuf)); + Set("0011100x00011x", InstEmit.Iscadd, typeof(OpCodeAluImm)); + Set("000101xxxxxxxx", InstEmit.Iscadd, typeof(OpCodeAluImm32)); + Set("0101110000011x", InstEmit.Iscadd, typeof(OpCodeAluReg)); + Set("010010110101xx", InstEmit.Iset, typeof(OpCodeSetCbuf)); + Set("001101100101xx", InstEmit.Iset, typeof(OpCodeSetImm)); + Set("010110110101xx", InstEmit.Iset, typeof(OpCodeSetReg)); + Set("010010110110xx", InstEmit.Isetp, typeof(OpCodeSetCbuf)); + Set("0011011x0110xx", InstEmit.Isetp, typeof(OpCodeSetImm)); + Set("010110110110xx", InstEmit.Isetp, typeof(OpCodeSetReg)); + Set("111000110011xx", InstEmit.Kil, typeof(OpCodeExit)); + Set("1110111110010x", InstEmit.Ldc, typeof(OpCodeLdc)); + Set("0100110001000x", InstEmit.Lop, typeof(OpCodeLopCbuf)); + Set("0011100001000x", InstEmit.Lop, typeof(OpCodeLopImm)); + Set("000001xxxxxxxx", InstEmit.Lop, typeof(OpCodeLopImm32)); + Set("0101110001000x", InstEmit.Lop, typeof(OpCodeLopReg)); + Set("0010000xxxxxxx", InstEmit.Lop3, typeof(OpCodeLopCbuf)); + Set("001111xxxxxxxx", InstEmit.Lop3, typeof(OpCodeLopImm)); + Set("0101101111100x", InstEmit.Lop3, typeof(OpCodeLopReg)); + Set("0100110010011x", InstEmit.Mov, typeof(OpCodeAluCbuf)); + Set("0011100x10011x", InstEmit.Mov, typeof(OpCodeAluImm)); + Set("000000010000xx", InstEmit.Mov, typeof(OpCodeAluImm32)); + Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg)); + Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith)); + Set("1111101111100x", InstEmit.Out, typeof(OpCode)); + Set("0101000010010x", InstEmit.Psetp, typeof(OpCodePsetp)); + Set("0100110010010x", InstEmit.Rro, typeof(OpCodeFArithCbuf)); + Set("0011100x10010x", InstEmit.Rro, typeof(OpCodeFArithImm)); + Set("0101110010010x", InstEmit.Rro, typeof(OpCodeFArithReg)); + Set("0100110010100x", InstEmit.Sel, typeof(OpCodeAluCbuf)); + Set("0011100010100x", InstEmit.Sel, typeof(OpCodeAluImm)); + Set("0101110010100x", InstEmit.Sel, typeof(OpCodeAluReg)); + Set("0100110001001x", InstEmit.Shl, typeof(OpCodeAluCbuf)); + Set("0011100x01001x", InstEmit.Shl, typeof(OpCodeAluImm)); + Set("0101110001001x", InstEmit.Shl, typeof(OpCodeAluReg)); + Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf)); + Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm)); + Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg)); + Set("111000101001xx", InstEmit.Ssy, typeof(OpCodeSsy)); + Set("1111000011111x", InstEmit.Sync, typeof(OpCodeSync)); + Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex)); + Set("1101111010111x", InstEmit.Tex_B, typeof(OpCodeTex)); + Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs)); + Set("1101x01xxxxxxx", InstEmit.Texs, typeof(OpCodeTlds)); + Set("1101x11100xxxx", InstEmit.Texs, typeof(OpCodeTld4s)); + Set("11011100xx111x", InstEmit.Tld, typeof(OpCodeTld)); + Set("11011101xx111x", InstEmit.Tld_B, typeof(OpCodeTld)); + Set("110010xxxx111x", InstEmit.Tld4, typeof(OpCodeTld4)); + Set("1101111101001x", InstEmit.Txq, typeof(OpCodeTex)); + Set("1101111101010x", InstEmit.Txq_B, typeof(OpCodeTex)); + Set("0100111xxxxxxx", InstEmit.Xmad, typeof(OpCodeAluCbuf)); + Set("0011011x00xxxx", InstEmit.Xmad, typeof(OpCodeAluImm)); + Set("010100010xxxxx", InstEmit.Xmad, typeof(OpCodeAluRegCbuf)); + Set("0101101100xxxx", InstEmit.Xmad, typeof(OpCodeAluReg)); +#endregion + } + + private static void Set(string encoding, InstEmitter emitter, Type opCodeType) + { + 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; + + TableEntry entry = new TableEntry(emitter, opCodeType, xBits); + + for (int index = 0; index < (1 << xBits); index++) + { + value &= xMask; + + for (int X = 0; X < xBits; X++) + { + value |= ((index >> X) & 1) << xPos[X]; + } + + if (_opCodes[value] == null || _opCodes[value].XBits > xBits) + { + _opCodes[value] = entry; + } + } + } + + public static (InstEmitter emitter, Type opCodeType) GetEmitter(long OpCode) + { + TableEntry entry = _opCodes[(ulong)OpCode >> (64 - EncodingBits)]; + + if (entry != null) + { + return (entry.Emitter, entry.OpCodeType); + } + + return (null, null); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs new file mode 100644 index 000000000..da8756b91 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTex : OpCodeTexture + { + public OpCodeTex(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + HasOffset = opCode.Extract(54); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs new file mode 100644 index 000000000..0822c4c07 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexs : OpCodeTextureScalar + { + public TextureScalarType Type => (TextureScalarType)RawType; + + public OpCodeTexs(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs new file mode 100644 index 000000000..7a7e8f46e --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs @@ -0,0 +1,42 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexture : OpCode + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rb { get; } + + public bool IsArray { get; } + + public TextureDimensions Dimensions { get; } + + public int ComponentMask { get; } + + public int Immediate { get; } + + public TextureLodMode LodMode { get; protected set; } + + public bool HasOffset { get; protected set; } + public bool HasDepthCompare { get; protected set; } + public bool IsMultisample { get; protected set; } + + public OpCodeTexture(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + IsArray = opCode.Extract(28); + + Dimensions = (TextureDimensions)opCode.Extract(29, 2); + + ComponentMask = opCode.Extract(31, 4); + + Immediate = opCode.Extract(36, 13); + + LodMode = (TextureLodMode)opCode.Extract(55, 3); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs new file mode 100644 index 000000000..4389f4531 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs @@ -0,0 +1,61 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTextureScalar : OpCode + { +#region "Component mask LUT" + private const int ____ = 0x0; + private const int R___ = 0x1; + private const int _G__ = 0x2; + private const int RG__ = 0x3; + private const int __B_ = 0x4; + private const int RGB_ = 0x7; + private const int ___A = 0x8; + private const int R__A = 0x9; + private const int _G_A = 0xa; + private const int RG_A = 0xb; + private const int __BA = 0xc; + private const int R_BA = 0xd; + private const int _GBA = 0xe; + private const int RGBA = 0xf; + + private static int[,] _maskLut = new int[,] + { + { R___, _G__, __B_, ___A, RG__, R__A, _G_A, __BA }, + { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ } + }; +#endregion + + public Register Rd0 { get; } + public Register Ra { get; } + public Register Rb { get; } + public Register Rd1 { get; } + + public int Immediate { get; } + + public int ComponentMask { get; } + + protected int RawType; + + public bool IsFp16 { get; } + + public OpCodeTextureScalar(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd0 = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + Rd1 = new Register(opCode.Extract(28, 8), RegisterType.Gpr); + + Immediate = opCode.Extract(36, 13); + + int compSel = opCode.Extract(50, 3); + + RawType = opCode.Extract(53, 4); + + IsFp16 = !opCode.Extract(59); + + ComponentMask = _maskLut[Rd1.IsRZ ? 0 : 1, compSel]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs new file mode 100644 index 000000000..61bd900b2 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld : OpCodeTexture + { + public OpCodeTld(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasOffset = opCode.Extract(35); + + IsMultisample = opCode.Extract(50); + + bool isLL = opCode.Extract(55); + + LodMode = isLL + ? TextureLodMode.LodLevel + : TextureLodMode.LodZero; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs new file mode 100644 index 000000000..485edf936 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4 : OpCodeTexture + { + public TextureGatherOffset Offset { get; } + + public int GatherCompIndex { get; } + + public OpCodeTld4(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + Offset = (TextureGatherOffset)opCode.Extract(54, 2); + + GatherCompIndex = opCode.Extract(56, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs new file mode 100644 index 000000000..0d7b84606 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4s : OpCodeTextureScalar + { + public bool HasDepthCompare { get; } + public bool HasOffset { get; } + + public int GatherCompIndex { get; } + + public OpCodeTld4s(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + HasOffset = opCode.Extract(51); + + GatherCompIndex = opCode.Extract(52, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs new file mode 100644 index 000000000..e117721e9 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTlds : OpCodeTextureScalar + { + public TexelLoadScalarType Type => (TexelLoadScalarType)RawType; + + public OpCodeTlds(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/Register.cs b/Ryujinx.Graphics/Shader/Decoders/Register.cs new file mode 100644 index 000000000..30840d8c0 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/Register.cs @@ -0,0 +1,36 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + struct Register : IEquatable + { + public int Index { get; } + + public RegisterType Type { get; } + + public bool IsRZ => Type == RegisterType.Gpr && Index == RegisterConsts.RegisterZeroIndex; + public bool IsPT => Type == RegisterType.Predicate && Index == RegisterConsts.PredicateTrueIndex; + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((ushort)Type << 16); + } + + public override bool Equals(object obj) + { + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return other.Index == Index && + other.Type == Type; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/RegisterConsts.cs b/Ryujinx.Graphics/Shader/Decoders/RegisterConsts.cs new file mode 100644 index 000000000..d381f9543 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/RegisterConsts.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class RegisterConsts + { + public const int GprsCount = 255; + public const int PredsCount = 7; + public const int FlagsCount = 4; + public const int TotalCount = GprsCount + PredsCount + FlagsCount; + + public const int RegisterZeroIndex = GprsCount; + public const int PredicateTrueIndex = PredsCount; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/RegisterType.cs b/Ryujinx.Graphics/Shader/Decoders/RegisterType.cs new file mode 100644 index 000000000..648f816a2 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/RegisterType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum RegisterType + { + Flag, + Gpr, + Predicate, + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs b/Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs new file mode 100644 index 000000000..13bb08dc8 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum RoundingMode + { + ToNearest = 0, + TowardsNegativeInfinity = 1, + TowardsPositiveInfinity = 2, + TowardsZero = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs b/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs new file mode 100644 index 000000000..cef5778a5 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TexelLoadScalarType + { + Texture1DLodZero = 0x0, + Texture1DLodLevel = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodZeroOffset = 0x4, + Texture2DLodLevel = 0x5, + Texture2DLodZeroMultisample = 0x6, + Texture3DLodZero = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DLodLevelOffset = 0xc + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs b/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs new file mode 100644 index 000000000..dbdf1927f --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureDimensions + { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCube = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs b/Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs new file mode 100644 index 000000000..4e9ade26a --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureGatherOffset + { + None = 0, + Offset = 1, + Offsets = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs b/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs new file mode 100644 index 000000000..0cc6f7143 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureLodMode + { + None = 0, + LodZero = 1, + LodBias = 2, + LodLevel = 3, + LodBiasA = 4, //? + LodLevelA = 5 //? + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs b/Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs new file mode 100644 index 000000000..ea35b1d1c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureProperty + { + Dimensions = 0x1, + Type = 0x2, + SamplePos = 0x5, + Filter = 0xa, + Lod = 0xc, + Wrap = 0xe, + BorderColor = 0x10 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs b/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs new file mode 100644 index 000000000..0055174b4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureScalarType + { + Texture1DLodZero = 0x0, + Texture2D = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodLevel = 0x3, + Texture2DDepthCompare = 0x4, + Texture2DLodLevelDepthCompare = 0x5, + Texture2DLodZeroDepthCompare = 0x6, + Texture2DArray = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DArrayLodZeroDepthCompare = 0x9, + Texture3D = 0xa, + Texture3DLodZero = 0xb, + TextureCube = 0xc, + TextureCubeLodLevel = 0xd + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs b/Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs new file mode 100644 index 000000000..949a2ef70 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum XmadCMode + { + Cfull = 0, + Clo = 1, + Chi = 2, + Csfu = 3, + Cbcc = 4 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs new file mode 100644 index 000000000..f7815e233 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs @@ -0,0 +1,684 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bfe(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isReverse = op.RawOpCode.Extract(40); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + if (isReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + Operand position = context.BitwiseAnd(srcB, Const(0xff)); + + Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8)); + + Operand res = isSigned + ? context.BitfieldExtractS32(srcA, position, size) + : context.BitfieldExtractU32(srcA, position, size); + + context.Copy(GetDest(context), res); + + //TODO: CC, X, corner cases + } + + public static void Iadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateA = false, negateB = false; + + if (!(op is OpCodeAluImm32)) + { + negateB = op.RawOpCode.Extract(48); + negateA = op.RawOpCode.Extract(49); + } + + Operand srcA = context.INegate(GetSrcA(context), negateA); + Operand srcB = context.INegate(GetSrcB(context), negateB); + + Operand res = context.IAdd(srcA, srcB); + + bool isSubtraction = negateA || negateB; + + if (op.Extended) + { + //Add carry, or subtract borrow. + res = context.IAdd(res, isSubtraction + ? context.BitwiseNot(GetCF(context)) + : context.BitwiseAnd(GetCF(context), Const(1))); + } + + SetIaddFlags(context, res, srcA, srcB, op.SetCondCode, op.Extended, isSubtraction); + + context.Copy(GetDest(context), res); + } + + public static void Iadd3(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + IntegerHalfPart partC = (IntegerHalfPart)op.RawOpCode.Extract(31, 2); + IntegerHalfPart partB = (IntegerHalfPart)op.RawOpCode.Extract(33, 2); + IntegerHalfPart partA = (IntegerHalfPart)op.RawOpCode.Extract(35, 2); + + IntegerShift mode = (IntegerShift)op.RawOpCode.Extract(37, 2); + + bool negateC = op.RawOpCode.Extract(49); + bool negateB = op.RawOpCode.Extract(50); + bool negateA = op.RawOpCode.Extract(51); + + Operand Extend(Operand src, IntegerHalfPart part) + { + if (!(op is OpCodeAluReg) || part == IntegerHalfPart.B32) + { + return src; + } + + if (part == IntegerHalfPart.H0) + { + return context.BitwiseAnd(src, Const(0xffff)); + } + else if (part == IntegerHalfPart.H1) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + //TODO: Warning. + } + + return src; + } + + Operand srcA = context.INegate(Extend(GetSrcA(context), partA), negateA); + Operand srcB = context.INegate(Extend(GetSrcB(context), partB), negateB); + Operand srcC = context.INegate(Extend(GetSrcC(context), partC), negateC); + + Operand res = context.IAdd(srcA, srcB); + + if (op is OpCodeAluReg && mode != IntegerShift.NoShift) + { + if (mode == IntegerShift.ShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + else if (mode == IntegerShift.ShiftRight) + { + res = context.ShiftRightU32(res, Const(16)); + } + else + { + //TODO: Warning. + } + } + + res = context.IAdd(res, srcC); + + context.Copy(GetDest(context), res); + + //TODO: CC, X, corner cases + } + + public static void Imnmx(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isSignedInt = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand resMin = isSignedInt + ? context.IMinimumS32(srcA, srcB) + : context.IMinimumU32(srcA, srcB); + + Operand resMax = isSignedInt + ? context.IMaximumS32(srcA, srcB) + : context.IMaximumU32(srcA, srcB); + + Operand pred = GetPredicate39(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax)); + + SetZnFlags(context, dest, op.SetCondCode); + + //TODO: X flags. + } + + public static void Iscadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateA = false, negateB = false; + + if (!(op is OpCodeAluImm32)) + { + negateB = op.RawOpCode.Extract(48); + negateA = op.RawOpCode.Extract(49); + } + + int shift = op is OpCodeAluImm32 + ? op.RawOpCode.Extract(53, 5) + : op.RawOpCode.Extract(39, 5); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + srcA = context.ShiftLeft(srcA, Const(shift)); + + srcA = context.INegate(srcA, negateA); + srcB = context.INegate(srcB, negateB); + + Operand res = context.IAdd(srcA, srcB); + + context.Copy(GetDest(context), res); + + //TODO: CC, X + } + + public static void Iset(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool boolFloat = op.RawOpCode.Extract(44); + bool isSigned = op.RawOpCode.Extract(48); + + IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned); + + Operand pred = GetPredicate39(context); + + res = GetPredLogicalOp(context, op.LogicalOp, res, pred); + + Operand dest = GetDest(context); + + if (boolFloat) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + } + else + { + context.Copy(dest, res); + } + + //TODO: CC, X + } + + public static void Isetp(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool isSigned = op.RawOpCode.Extract(48); + + IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand p0Res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Lop(EmitterContext context) + { + IOpCodeLop op = (IOpCodeLop)context.CurrOp; + + Operand srcA = context.BitwiseNot(GetSrcA(context), op.InvertA); + Operand srcB = context.BitwiseNot(GetSrcB(context), op.InvertB); + + Operand res = srcB; + + switch (op.LogicalOp) + { + case LogicalOperation.And: res = context.BitwiseAnd (srcA, srcB); break; + case LogicalOperation.Or: res = context.BitwiseOr (srcA, srcB); break; + case LogicalOperation.ExclusiveOr: res = context.BitwiseExclusiveOr(srcA, srcB); break; + } + + EmitLopPredWrite(context, op, res); + + Operand dest = GetDest(context); + + context.Copy(dest, res); + + SetZnFlags(context, dest, op.SetCondCode, op.Extended); + } + + public static void Lop3(EmitterContext context) + { + IOpCodeLop op = (IOpCodeLop)context.CurrOp; + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + bool regVariant = op is OpCodeLopReg; + + int truthTable = regVariant + ? op.RawOpCode.Extract(28, 8) + : op.RawOpCode.Extract(48, 8); + + Operand res = Lop3Expression.GetFromTruthTable(context, srcA, srcB, srcC, truthTable); + + if (regVariant) + { + EmitLopPredWrite(context, op, res); + } + + Operand dest = GetDest(context); + + context.Copy(dest, res); + + SetZnFlags(context, dest, op.SetCondCode, op.Extended); + } + + public static void Psetp(EmitterContext context) + { + OpCodePsetp op = (OpCodePsetp)context.CurrOp; + + bool invertA = op.RawOpCode.Extract(15); + bool invertB = op.RawOpCode.Extract(32); + + Operand srcA = context.BitwiseNot(Register(op.Predicate12), invertA); + Operand srcB = context.BitwiseNot(Register(op.Predicate29), invertB); + + Operand p0Res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Rro(EmitterContext context) + { + //This is the range reduction operator, + //we translate it as a simple move, as it + //should be always followed by a matching + //MUFU instruction. + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = GetSrcB(context); + + srcB = context.FPAbsNeg(srcB, absoluteB, negateB); + + context.Copy(GetDest(context), srcB); + } + + public static void Shl(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isMasked = op.RawOpCode.Extract(39); + + Operand srcB = GetSrcB(context); + + if (isMasked) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = context.ShiftLeft(GetSrcA(context), srcB); + + if (!isMasked) + { + //Clamped shift value. + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, Const(0)); + } + + //TODO: X, CC + + context.Copy(GetDest(context), res); + } + + public static void Shr(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isMasked = op.RawOpCode.Extract(39); + bool isReverse = op.RawOpCode.Extract(40); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + if (isReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + if (isMasked) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = isSigned + ? context.ShiftRightS32(srcA, srcB) + : context.ShiftRightU32(srcA, srcB); + + if (!isMasked) + { + //Clamped shift value. + Operand resShiftBy32; + + if (isSigned) + { + resShiftBy32 = context.ShiftRightS32(srcA, Const(31)); + } + else + { + resShiftBy32 = Const(0); + } + + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, resShiftBy32); + } + + //TODO: X, CC + + context.Copy(GetDest(context), res); + } + + public static void Xmad(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool signedA = context.CurrOp.RawOpCode.Extract(48); + bool signedB = context.CurrOp.RawOpCode.Extract(49); + bool highA = context.CurrOp.RawOpCode.Extract(53); + bool highB = false; + + XmadCMode mode; + + if (op is OpCodeAluReg) + { + highB = context.CurrOp.RawOpCode.Extract(35); + + mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 3); + } + else + { + mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 2); + + if (!(op is OpCodeAluImm)) + { + highB = context.CurrOp.RawOpCode.Extract(52); + } + } + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + //XMAD immediates are 16-bits unsigned integers. + if (srcB.Type == OperandType.Constant) + { + srcB = Const(srcB.Value & 0xffff); + } + + Operand Extend16To32(Operand src, bool high, bool signed) + { + if (signed && high) + { + return context.ShiftRightS32(src, Const(16)); + } + else if (signed) + { + return context.BitfieldExtractS32(src, Const(0), Const(16)); + } + else if (high) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + return context.BitwiseAnd(src, Const(0xffff)); + } + } + + srcA = Extend16To32(srcA, highA, signedA); + srcB = Extend16To32(srcB, highB, signedB); + + bool productShiftLeft = false; + bool merge = false; + + if (!(op is OpCodeAluRegCbuf)) + { + productShiftLeft = context.CurrOp.RawOpCode.Extract(36); + merge = context.CurrOp.RawOpCode.Extract(37); + } + + bool extended; + + if ((op is OpCodeAluReg) || (op is OpCodeAluImm)) + { + extended = context.CurrOp.RawOpCode.Extract(38); + } + else + { + extended = context.CurrOp.RawOpCode.Extract(54); + } + + Operand res = context.IMultiply(srcA, srcB); + + if (productShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + + switch (mode) + { + case XmadCMode.Cfull: break; + + case XmadCMode.Clo: srcC = Extend16To32(srcC, high: false, signed: false); break; + case XmadCMode.Chi: srcC = Extend16To32(srcC, high: true, signed: false); break; + + case XmadCMode.Cbcc: + { + srcC = context.IAdd(srcC, context.ShiftLeft(GetSrcB(context), Const(16))); + + break; + } + + case XmadCMode.Csfu: + { + Operand signAdjustA = context.ShiftLeft(context.ShiftRightU32(srcA, Const(31)), Const(16)); + Operand signAdjustB = context.ShiftLeft(context.ShiftRightU32(srcB, Const(31)), Const(16)); + + srcC = context.ISubtract(srcC, context.IAdd(signAdjustA, signAdjustB)); + + break; + } + + default: /* TODO: Warning */ break; + } + + Operand product = res; + + if (extended) + { + //Add with carry. + res = context.IAdd(res, context.BitwiseAnd(GetCF(context), Const(1))); + } + else + { + //Add (no carry in). + res = context.IAdd(res, srcC); + } + + SetIaddFlags(context, res, product, srcC, op.SetCondCode, extended); + + if (merge) + { + res = context.BitwiseAnd(res, Const(0xffff)); + res = context.BitwiseOr(res, context.ShiftLeft(GetSrcB(context), Const(16))); + } + + context.Copy(GetDest(context), res); + } + + private static Operand GetIntComparison( + EmitterContext context, + IntegerCondition cond, + Operand srcA, + Operand srcB, + bool isSigned) + { + Operand res; + + if (cond == IntegerCondition.Always) + { + res = Const(IrConsts.True); + } + else if (cond == IntegerCondition.Never) + { + res = Const(IrConsts.False); + } + else + { + Instruction inst; + + switch (cond) + { + case IntegerCondition.Less: inst = Instruction.CompareLessU32; break; + case IntegerCondition.Equal: inst = Instruction.CompareEqual; break; + case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqualU32; break; + case IntegerCondition.Greater: inst = Instruction.CompareGreaterU32; break; + case IntegerCondition.NotEqual: inst = Instruction.CompareNotEqual; break; + case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqualU32; break; + + default: throw new InvalidOperationException($"Unexpected condition \"{cond}\"."); + } + + if (isSigned) + { + switch (cond) + { + case IntegerCondition.Less: inst = Instruction.CompareLess; break; + case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break; + case IntegerCondition.Greater: inst = Instruction.CompareGreater; break; + case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break; + } + } + + res = context.Add(inst, Local(), srcA, srcB); + } + + return res; + } + + private static void EmitLopPredWrite(EmitterContext context, IOpCodeLop op, Operand result) + { + if (op is OpCodeLop opLop && !opLop.Predicate48.IsPT) + { + Operand pRes; + + if (opLop.CondOp == ConditionalOperation.False) + { + pRes = Const(IrConsts.False); + } + else if (opLop.CondOp == ConditionalOperation.True) + { + pRes = Const(IrConsts.True); + } + else if (opLop.CondOp == ConditionalOperation.Zero) + { + pRes = context.ICompareEqual(result, Const(0)); + } + else /* if (opLop.CondOp == ConditionalOperation.NotZero) */ + { + pRes = context.ICompareNotEqual(result, Const(0)); + } + + context.Copy(Register(opLop.Predicate48), pRes); + } + } + + private static void SetIaddFlags( + EmitterContext context, + Operand res, + Operand srcA, + Operand srcB, + bool setCC, + bool extended, + bool isSubtraction = false) + { + if (!setCC) + { + return; + } + + if (!extended || isSubtraction) + { + //C = d < a + context.Copy(GetCF(context), context.ICompareLessUnsigned(res, srcA)); + } + else + { + //C = (d == a && CIn) || d < a + Operand tempC0 = context.ICompareEqual (res, srcA); + Operand tempC1 = context.ICompareLessUnsigned(res, srcA); + + tempC0 = context.BitwiseAnd(tempC0, GetCF(context)); + + context.Copy(GetCF(context), context.BitwiseOr(tempC0, tempC1)); + } + + //V = (d ^ a) & ~(a ^ b) < 0 + Operand tempV0 = context.BitwiseExclusiveOr(res, srcA); + Operand tempV1 = context.BitwiseExclusiveOr(srcA, srcB); + + tempV1 = context.BitwiseNot(tempV1); + + Operand tempV = context.BitwiseAnd(tempV0, tempV1); + + context.Copy(GetVF(context), context.ICompareLess(tempV, Const(0))); + + SetZnFlags(context, res, setCC: true, extended: extended); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs new file mode 100644 index 000000000..b5bde1a1c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,88 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitAluHelper + { + public static int GetIntMin(IntegerType type) + { + switch (type) + { + case IntegerType.U8: return byte.MinValue; + case IntegerType.S8: return sbyte.MinValue; + case IntegerType.U16: return ushort.MinValue; + case IntegerType.S16: return short.MinValue; + case IntegerType.U32: return (int)uint.MinValue; + case IntegerType.S32: return int.MinValue; + } + + throw new ArgumentException($"The type \"{type}\" is not a supported int type."); + } + + public static int GetIntMax(IntegerType type) + { + switch (type) + { + case IntegerType.U8: return byte.MaxValue; + case IntegerType.S8: return sbyte.MaxValue; + case IntegerType.U16: return ushort.MaxValue; + case IntegerType.S16: return short.MaxValue; + case IntegerType.U32: return unchecked((int)uint.MaxValue); + case IntegerType.S32: return int.MaxValue; + } + + throw new ArgumentException($"The type \"{type}\" is not a supported int type."); + } + + public static Operand GetPredLogicalOp( + EmitterContext context, + LogicalOperation logicalOp, + Operand input, + Operand pred) + { + switch (logicalOp) + { + case LogicalOperation.And: return context.BitwiseAnd (input, pred); + case LogicalOperation.Or: return context.BitwiseOr (input, pred); + case LogicalOperation.ExclusiveOr: return context.BitwiseExclusiveOr(input, pred); + } + + return input; + } + + public static void SetZnFlags(EmitterContext context, Operand dest, bool setCC, bool extended = false) + { + if (!setCC) + { + return; + } + + if (extended) + { + //When the operation is extended, it means we are doing + //the operation on a long word with any number of bits, + //so we need to AND the zero flag from result with the + //previous result when extended is specified, to ensure + //we have ZF set only if all words are zero, and not just + //the last one. + Operand oldZF = GetZF(context); + + Operand res = context.BitwiseAnd(context.ICompareEqual(dest, Const(0)), oldZF); + + context.Copy(GetZF(context), res); + } + else + { + context.Copy(GetZF(context), context.ICompareEqual(dest, Const(0))); + } + + context.Copy(GetNF(context), context.ICompareLess(dest, Const(0))); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs new file mode 100644 index 000000000..f5e9af036 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs @@ -0,0 +1,213 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void F2F(EmitterContext context) + { + OpCodeFArith op = (OpCodeFArith)context.CurrOp; + + FPType srcType = (FPType)op.RawOpCode.Extract(8, 2); + FPType dstType = (FPType)op.RawOpCode.Extract(10, 2); + + bool pass = op.RawOpCode.Extract(40); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + pass &= op.RoundingMode == RoundingMode.TowardsNegativeInfinity; + + Operand srcB = context.FPAbsNeg(GetSrcB(context, srcType), absoluteB, negateB); + + if (!pass) + { + switch (op.RoundingMode) + { + case RoundingMode.TowardsNegativeInfinity: + srcB = context.FPFloor(srcB); + break; + + case RoundingMode.TowardsPositiveInfinity: + srcB = context.FPCeiling(srcB); + break; + + case RoundingMode.TowardsZero: + srcB = context.FPTruncate(srcB); + break; + } + } + + srcB = context.FPSaturate(srcB, op.Saturate); + + WriteFP(context, dstType, srcB); + + //TODO: CC. + } + + public static void F2I(EmitterContext context) + { + OpCodeFArith op = (OpCodeFArith)context.CurrOp; + + IntegerType intType = (IntegerType)op.RawOpCode.Extract(8, 2); + + bool isSmallInt = intType <= IntegerType.U16; + + FPType floatType = (FPType)op.RawOpCode.Extract(10, 2); + + bool isSignedInt = op.RawOpCode.Extract(12); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + if (isSignedInt) + { + intType |= IntegerType.S8; + } + + Operand srcB = context.FPAbsNeg(GetSrcB(context, floatType), absoluteB, negateB); + + switch (op.RoundingMode) + { + case RoundingMode.TowardsNegativeInfinity: + srcB = context.FPFloor(srcB); + break; + + case RoundingMode.TowardsPositiveInfinity: + srcB = context.FPCeiling(srcB); + break; + + case RoundingMode.TowardsZero: + srcB = context.FPTruncate(srcB); + break; + } + + srcB = context.FPConvertToS32(srcB); + + //TODO: S/U64, conversion overflow handling. + if (intType != IntegerType.S32) + { + int min = GetIntMin(intType); + int max = GetIntMax(intType); + + srcB = isSignedInt + ? context.IClampS32(srcB, Const(min), Const(max)) + : context.IClampU32(srcB, Const(min), Const(max)); + } + + Operand dest = GetDest(context); + + context.Copy(dest, srcB); + + //TODO: CC. + } + + public static void I2F(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + FPType dstType = (FPType)op.RawOpCode.Extract(8, 2); + + IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2); + + bool isSmallInt = srcType <= IntegerType.U16; + + bool isSignedInt = op.RawOpCode.Extract(13); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = context.IAbsNeg(GetSrcB(context), absoluteB, negateB); + + if (isSmallInt) + { + int size = srcType == IntegerType.U16 ? 16 : 8; + + srcB = isSignedInt + ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size)); + } + + srcB = isSignedInt + ? context.IConvertS32ToFP(srcB) + : context.IConvertU32ToFP(srcB); + + WriteFP(context, dstType, srcB); + + //TODO: CC. + } + + public static void I2I(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + IntegerType dstType = (IntegerType)op.RawOpCode.Extract(8, 2); + IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2); + + if (srcType == IntegerType.U64 || dstType == IntegerType.U64) + { + //TODO: Warning. This instruction doesn't support 64-bits integers + } + + bool srcIsSmallInt = srcType <= IntegerType.U16; + + bool dstIsSignedInt = op.RawOpCode.Extract(12); + bool srcIsSignedInt = op.RawOpCode.Extract(13); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = GetSrcB(context); + + if (srcIsSmallInt) + { + int size = srcType == IntegerType.U16 ? 16 : 8; + + srcB = srcIsSignedInt + ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size)); + } + + srcB = context.IAbsNeg(srcB, absoluteB, negateB); + + if (op.Saturate) + { + if (dstIsSignedInt) + { + dstType |= IntegerType.S8; + } + + int min = GetIntMin(dstType); + int max = GetIntMax(dstType); + + srcB = dstIsSignedInt + ? context.IClampS32(srcB, Const(min), Const(max)) + : context.IClampU32(srcB, Const(min), Const(max)); + } + + context.Copy(GetDest(context), srcB); + + //TODO: CC. + } + + private static void WriteFP(EmitterContext context, FPType type, Operand srcB) + { + Operand dest = GetDest(context); + + if (type == FPType.FP32) + { + context.Copy(dest, srcB); + } + else if (type == FPType.FP16) + { + context.Copy(dest, context.PackHalf2x16(srcB, ConstF(0))); + } + else + { + //TODO. + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs new file mode 100644 index 000000000..72492470c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs @@ -0,0 +1,369 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Fadd(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool absoluteA = op.AbsoluteA, absoluteB, negateA, negateB; + + if (op is OpCodeFArithImm32) + { + negateB = op.RawOpCode.Extract(53); + negateA = op.RawOpCode.Extract(56); + absoluteB = op.RawOpCode.Extract(57); + } + else + { + negateB = op.RawOpCode.Extract(45); + negateA = op.RawOpCode.Extract(48); + absoluteB = op.RawOpCode.Extract(49); + } + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPAdd(srcA, srcB), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Ffma(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(48); + bool negateC = op.RawOpCode.Extract(49); + + Operand srcA = GetSrcA(context); + + Operand srcB = context.FPNegate(GetSrcB(context), negateB); + Operand srcC = context.FPNegate(GetSrcC(context), negateC); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fmnmx(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool absoluteA = op.AbsoluteA; + bool negateB = op.RawOpCode.Extract(45); + bool negateA = op.RawOpCode.Extract(48); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand resMin = context.FPMinimum(srcA, srcB); + Operand resMax = context.FPMaximum(srcA, srcB); + + Operand pred = GetPredicate39(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fmul(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = !(op is OpCodeFArithImm32) && op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + + Operand srcB = context.FPNegate(GetSrcB(context), negateB); + + switch (op.Scale) + { + case FmulScale.None: break; + + case FmulScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break; + case FmulScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break; + case FmulScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break; + case FmulScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break; + case FmulScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break; + case FmulScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break; + + default: break; //TODO: Warning. + } + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPMultiply(srcA, srcB), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fset(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4); + + bool negateA = op.RawOpCode.Extract(43); + bool absoluteB = op.RawOpCode.Extract(44); + bool boolFloat = op.RawOpCode.Extract(52); + bool negateB = op.RawOpCode.Extract(53); + bool absoluteA = op.RawOpCode.Extract(54); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand res = GetFPComparison(context, cmpOp, srcA, srcB); + + Operand pred = GetPredicate39(context); + + res = GetPredLogicalOp(context, op.LogicalOp, res, pred); + + Operand dest = GetDest(context); + + if (boolFloat) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + } + else + { + context.Copy(dest, res); + } + + //TODO: CC, X + } + + public static void Fsetp(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4); + + bool absoluteA = op.RawOpCode.Extract(7); + bool negateA = op.RawOpCode.Extract(43); + bool absoluteB = op.RawOpCode.Extract(44); + + Operand srcA = context.FPAbsNeg (GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsolute(GetSrcB(context), absoluteB); + + Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Hadd2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: true); + } + + public static void Hfma2(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] srcA = GetHfmaSrcA(context); + Operand[] srcB = GetHfmaSrcB(context); + Operand[] srcC = GetHfmaSrcC(context); + + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + res[index] = context.FPFusedMultiplyAdd(srcA[index], srcB[index], srcC[index]); + + res[index] = context.FPSaturate(res[index], op.Saturate); + } + + context.Copy(GetDest(context), GetHalfPacked(context, res)); + } + + public static void Hmul2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: false); + } + + private static void Hadd2Hmul2Impl(EmitterContext context, bool isAdd) + { + OpCode op = context.CurrOp; + + bool saturate = op.RawOpCode.Extract(op is OpCodeAluImm32 ? 52 : 32); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + if (isAdd) + { + res[index] = context.FPAdd(srcA[index], srcB[index]); + } + else + { + res[index] = context.FPMultiply(srcA[index], srcB[index]); + } + + res[index] = context.FPSaturate(res[index], saturate); + } + + context.Copy(GetDest(context), GetHalfPacked(context, res)); + } + + public static void Mufu(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(48); + + Operand res = context.FPAbsNeg(GetSrcA(context), op.AbsoluteA, negateB); + + MufuOperation subOp = (MufuOperation)context.CurrOp.RawOpCode.Extract(20, 4); + + switch (subOp) + { + case MufuOperation.Cosine: + res = context.FPCosine(res); + break; + + case MufuOperation.Sine: + res = context.FPSine(res); + break; + + case MufuOperation.ExponentB2: + res = context.FPExponentB2(res); + break; + + case MufuOperation.LogarithmB2: + res = context.FPLogarithmB2(res); + break; + + case MufuOperation.Reciprocal: + res = context.FPReciprocal(res); + break; + + case MufuOperation.ReciprocalSquareRoot: + res = context.FPReciprocalSquareRoot(res); + break; + + case MufuOperation.SquareRoot: + res = context.FPSquareRoot(res); + break; + + default: /* TODO */ break; + } + + context.Copy(GetDest(context), context.FPSaturate(res, op.Saturate)); + } + + private static Operand GetFPComparison( + EmitterContext context, + Condition cond, + Operand srcA, + Operand srcB) + { + Operand res; + + if (cond == Condition.Always) + { + res = Const(IrConsts.True); + } + else if (cond == Condition.Never) + { + res = Const(IrConsts.False); + } + else if (cond == Condition.Nan || cond == Condition.Number) + { + res = context.BitwiseOr(context.IsNan(srcA), context.IsNan(srcB)); + + if (cond == Condition.Number) + { + res = context.BitwiseNot(res); + } + } + else + { + Instruction inst; + + switch (cond & ~Condition.Nan) + { + case Condition.Less: inst = Instruction.CompareLess; break; + case Condition.Equal: inst = Instruction.CompareEqual; break; + case Condition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break; + case Condition.Greater: inst = Instruction.CompareGreater; break; + case Condition.NotEqual: inst = Instruction.CompareNotEqual; break; + case Condition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break; + + default: throw new InvalidOperationException($"Unexpected condition \"{cond}\"."); + } + + res = context.Add(inst | Instruction.FP, Local(), srcA, srcB); + + if ((cond & Condition.Nan) != 0) + { + res = context.BitwiseOr(res, context.IsNan(srcA)); + res = context.BitwiseOr(res, context.IsNan(srcB)); + } + } + + return res; + } + + private static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC) + { + if (setCC) + { + context.Copy(GetZF(context), context.FPCompareEqual(dest, ConstF(0))); + context.Copy(GetNF(context), context.FPCompareLess (dest, ConstF(0))); + } + } + + private static Operand[] GetHfmaSrcA(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + return GetHalfSources(context, GetSrcA(context), op.SwizzleA); + } + + private static Operand[] GetHfmaSrcB(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] operands = GetHalfSources(context, GetSrcB(context), op.SwizzleB); + + return FPAbsNeg(context, operands, false, op.NegateB); + } + + private static Operand[] GetHfmaSrcC(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] operands = GetHalfSources(context, GetSrcC(context), op.SwizzleC); + + return FPAbsNeg(context, operands, false, op.NegateC); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs new file mode 100644 index 000000000..c4523f75c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs @@ -0,0 +1,107 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bra(EmitterContext context) + { + EmitBranch(context, context.CurrBlock.Branch.Address); + } + + public static void Exit(EmitterContext context) + { + OpCodeExit op = (OpCodeExit)context.CurrOp; + + //TODO: Figure out how this is supposed to work in the + //presence of other condition codes. + if (op.Condition == Condition.Always) + { + context.Return(); + } + } + + public static void Kil(EmitterContext context) + { + context.Discard(); + } + + public static void Ssy(EmitterContext context) + { + OpCodeSsy op = (OpCodeSsy)context.CurrOp; + + foreach (KeyValuePair kv in op.Syncs) + { + OpCodeSync opSync = kv.Key; + + Operand local = kv.Value; + + int ssyIndex = opSync.Targets[op]; + + context.Copy(local, Const(ssyIndex)); + } + } + + public static void Sync(EmitterContext context) + { + OpCodeSync op = (OpCodeSync)context.CurrOp; + + if (op.Targets.Count == 1) + { + //If we have only one target, then the SSY is basically + //a branch, we can produce better codegen for this case. + OpCodeSsy opSsy = op.Targets.Keys.First(); + + EmitBranch(context, opSsy.GetAbsoluteAddress()); + } + else + { + foreach (KeyValuePair kv in op.Targets) + { + OpCodeSsy opSsy = kv.Key; + + Operand label = context.GetLabel(opSsy.GetAbsoluteAddress()); + + Operand local = opSsy.Syncs[op]; + + int ssyIndex = kv.Value; + + context.BranchIfTrue(label, context.ICompareEqual(local, Const(ssyIndex))); + } + } + } + + private static void EmitBranch(EmitterContext context, ulong address) + { + //If we're branching to the next instruction, then the branch + //is useless and we can ignore it. + if (address == context.CurrOp.Address + 8) + { + return; + } + + Operand label = context.GetLabel(address); + + Operand pred = Register(context.CurrOp.Predicate); + + if (context.CurrOp.Predicate.IsPT) + { + context.Branch(label); + } + else if (context.CurrOp.InvertPredicate) + { + context.BranchIfFalse(label, pred); + } + else + { + context.BranchIfTrue(label, pred); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs new file mode 100644 index 000000000..e31528d02 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs @@ -0,0 +1,267 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitHelper + { + public static Operand GetZF(EmitterContext context) + { + return Register(0, RegisterType.Flag); + } + + public static Operand GetNF(EmitterContext context) + { + return Register(1, RegisterType.Flag); + } + + public static Operand GetCF(EmitterContext context) + { + return Register(2, RegisterType.Flag); + } + + public static Operand GetVF(EmitterContext context) + { + return Register(3, RegisterType.Flag); + } + + public static Operand GetDest(EmitterContext context) + { + return Register(((IOpCodeRd)context.CurrOp).Rd); + } + + public static Operand GetSrcA(EmitterContext context) + { + return Register(((IOpCodeRa)context.CurrOp).Ra); + } + + public static Operand GetSrcB(EmitterContext context, FPType floatType) + { + if (floatType == FPType.FP32) + { + return GetSrcB(context); + } + else if (floatType == FPType.FP16) + { + int h = context.CurrOp.RawOpCode.Extract(41, 1); + + return GetHalfSources(context, GetSrcB(context), FPHalfSwizzle.FP16)[h]; + } + else if (floatType == FPType.FP64) + { + //TODO. + } + + throw new ArgumentException($"Invalid floating point type \"{floatType}\"."); + } + + public static Operand GetSrcB(EmitterContext context) + { + switch (context.CurrOp) + { + case IOpCodeCbuf op: + return Cbuf(op.Slot, op.Offset); + + case IOpCodeImm op: + return Const(op.Immediate); + + case IOpCodeImmF op: + return ConstF(op.Immediate); + + case IOpCodeReg op: + return Register(op.Rb); + + case IOpCodeRegCbuf op: + return Register(op.Rc); + } + + throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\"."); + } + + public static Operand GetSrcC(EmitterContext context) + { + switch (context.CurrOp) + { + case IOpCodeRegCbuf op: + return Cbuf(op.Slot, op.Offset); + + case IOpCodeRc op: + return Register(op.Rc); + } + + throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\"."); + } + + public static Operand[] GetHalfSrcA(EmitterContext context) + { + OpCode op = context.CurrOp; + + bool absoluteA = false, negateA = false; + + if (op is IOpCodeCbuf || op is IOpCodeImm) + { + negateA = op.RawOpCode.Extract(43); + absoluteA = op.RawOpCode.Extract(44); + } + else if (op is IOpCodeReg) + { + absoluteA = op.RawOpCode.Extract(44); + } + else if (op is OpCodeAluImm32 && op.Emitter == InstEmit.Hadd2) + { + negateA = op.RawOpCode.Extract(56); + } + + FPHalfSwizzle swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(47, 2); + + Operand[] operands = GetHalfSources(context, GetSrcA(context), swizzle); + + return FPAbsNeg(context, operands, absoluteA, negateA); + } + + public static Operand[] GetHalfSrcB(EmitterContext context) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + bool absoluteB = false, negateB = false; + + if (op is IOpCodeReg) + { + swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(28, 2); + + absoluteB = op.RawOpCode.Extract(30); + negateB = op.RawOpCode.Extract(31); + } + else if (op is IOpCodeCbuf) + { + swizzle = FPHalfSwizzle.FP32; + + absoluteB = op.RawOpCode.Extract(54); + } + + Operand[] operands = GetHalfSources(context, GetSrcB(context), swizzle); + + return FPAbsNeg(context, operands, absoluteB, negateB); + } + + public static Operand[] FPAbsNeg(EmitterContext context, Operand[] operands, bool abs, bool neg) + { + for (int index = 0; index < operands.Length; index++) + { + operands[index] = context.FPAbsNeg(operands[index], abs, neg); + } + + return operands; + } + + public static Operand[] GetHalfSources(EmitterContext context, Operand src, FPHalfSwizzle swizzle) + { + switch (swizzle) + { + case FPHalfSwizzle.FP16: + return new Operand[] + { + context.UnpackHalf2x16Low (src), + context.UnpackHalf2x16High(src) + }; + + case FPHalfSwizzle.FP32: return new Operand[] { src, src }; + + case FPHalfSwizzle.DupH0: + return new Operand[] + { + context.UnpackHalf2x16Low(src), + context.UnpackHalf2x16Low(src) + }; + + case FPHalfSwizzle.DupH1: + return new Operand[] + { + context.UnpackHalf2x16High(src), + context.UnpackHalf2x16High(src) + }; + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + public static Operand GetHalfPacked(EmitterContext context, Operand[] results) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + if (!(op is OpCodeAluImm32)) + { + swizzle = (FPHalfSwizzle)context.CurrOp.RawOpCode.Extract(49, 2); + } + + switch (swizzle) + { + case FPHalfSwizzle.FP16: return context.PackHalf2x16(results[0], results[1]); + + case FPHalfSwizzle.FP32: return results[0]; + + case FPHalfSwizzle.DupH0: + { + Operand h1 = GetHalfDest(context, isHigh: true); + + return context.PackHalf2x16(results[0], h1); + } + + case FPHalfSwizzle.DupH1: + { + Operand h0 = GetHalfDest(context, isHigh: false); + + return context.PackHalf2x16(h0, results[1]); + } + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + public static Operand GetHalfDest(EmitterContext context, bool isHigh) + { + if (isHigh) + { + return context.UnpackHalf2x16High(GetDest(context)); + } + else + { + return context.UnpackHalf2x16Low(GetDest(context)); + } + } + + public static Operand GetPredicate39(EmitterContext context) + { + IOpCodeAlu op = (IOpCodeAlu)context.CurrOp; + + Operand local = Register(op.Predicate39); + + if (op.InvertP) + { + local = context.BitwiseNot(local); + } + + return local; + } + + public static Operand SignExtendTo32(EmitterContext context, Operand src, int srcBits) + { + return context.BitfieldExtractS32(src, Const(0), Const(srcBits)); + } + + public static Operand ZeroExtendTo32(EmitterContext context, Operand src, int srcBits) + { + int mask = (int)(0xffffffffu >> (32 - srcBits)); + + return context.BitwiseAnd(src, Const(mask)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs new file mode 100644 index 000000000..d81e97a13 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs @@ -0,0 +1,138 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Ald(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + Operand[] elems = new Operand[op.Count]; + + for (int index = 0; index < op.Count; index++) + { + Operand src = Attribute(op.AttributeOffset + index * 4); + + context.Copy(elems[index] = Local(), src); + } + + for (int index = 0; index < op.Count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + context.Copy(Register(rd), elems[index]); + } + } + + public static void Ast(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + for (int index = 0; index < op.Count; index++) + { + if (op.Rd.Index + index > RegisterConsts.RegisterZeroIndex) + { + break; + } + + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand dest = Attribute(op.AttributeOffset + index * 4); + + context.Copy(dest, Register(rd)); + } + } + + public static void Ipa(EmitterContext context) + { + OpCodeIpa op = (OpCodeIpa)context.CurrOp; + + Operand srcA = new Operand(OperandType.Attribute, op.AttributeOffset); + + Operand srcB = GetSrcB(context); + + context.Copy(GetDest(context), srcA); + } + + public static void Ldc(EmitterContext context) + { + OpCodeLdc op = (OpCodeLdc)context.CurrOp; + + if (op.Size > IntegerSize.B64) + { + //TODO: Warning. + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = op.Size == IntegerSize.B64 ? 2 : 1; + + Operand baseOffset = context.Copy(GetSrcA(context)); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand offset = context.IAdd(baseOffset, Const((op.Offset + index) * 4)); + + Operand value = context.LoadConstant(Const(op.Slot), offset); + + if (isSmallInt) + { + Operand shift = context.BitwiseAnd(baseOffset, Const(3)); + + value = context.ShiftRightU32(value, shift); + + switch (op.Size) + { + case IntegerSize.U8: value = ZeroExtendTo32(context, value, 8); break; + case IntegerSize.U16: value = ZeroExtendTo32(context, value, 16); break; + case IntegerSize.S8: value = SignExtendTo32(context, value, 8); break; + case IntegerSize.S16: value = SignExtendTo32(context, value, 16); break; + } + } + + context.Copy(Register(rd), value); + } + } + + public static void Out(EmitterContext context) + { + OpCode op = context.CurrOp; + + bool emit = op.RawOpCode.Extract(39); + bool cut = op.RawOpCode.Extract(40); + + if (!(emit || cut)) + { + //TODO: Warning. + } + + if (emit) + { + context.EmitVertex(); + } + + if (cut) + { + context.EndPrimitive(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs new file mode 100644 index 000000000..b74dbfd72 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Mov(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + context.Copy(GetDest(context), GetSrcB(context)); + } + + public static void Sel(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand pred = GetPredicate39(context); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand res = context.ConditionalSelect(pred, srcA, srcB); + + context.Copy(GetDest(context), res); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs new file mode 100644 index 000000000..1b19d901c --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs @@ -0,0 +1,776 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Tex(EmitterContext context) + { + Tex(context, TextureFlags.None); + } + + public static void Tex_B(EmitterContext context) + { + Tex(context, TextureFlags.Bindless); + } + + public static void Tld(EmitterContext context) + { + Tex(context, TextureFlags.IntCoords); + } + + public static void Tld_B(EmitterContext context) + { + Tex(context, TextureFlags.IntCoords | TextureFlags.Bindless); + } + + public static void Texs(EmitterContext context) + { + OpCodeTextureScalar op = (OpCodeTextureScalar)context.CurrOp; + + if (op.Rd0.IsRZ && op.Rd1.IsRZ) + { + return; + } + + List sourcesList = new List(); + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + void AddTextureOffset(int coordsCount, int stride, int size) + { + Operand packedOffs = Rb(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * stride), Const(size))); + } + } + + TextureType type; + TextureFlags flags; + + if (op is OpCodeTexs texsOp) + { + type = GetTextureType (texsOp.Type); + flags = GetTextureFlags(texsOp.Type); + + if ((type & TextureType.Array) != 0) + { + Operand arrayIndex = Ra(); + + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + + sourcesList.Add(arrayIndex); + + if ((type & TextureType.Shadow) != 0) + { + sourcesList.Add(Rb()); + } + + if ((flags & TextureFlags.LodLevel) != 0) + { + sourcesList.Add(ConstF(0)); + } + } + else + { + switch (texsOp.Type) + { + case TextureScalarType.Texture1DLodZero: + sourcesList.Add(Ra()); + break; + + case TextureScalarType.Texture2D: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TextureScalarType.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case TextureScalarType.Texture2DLodLevel: + case TextureScalarType.Texture2DDepthCompare: + case TextureScalarType.Texture3D: + case TextureScalarType.TextureCube: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TextureScalarType.Texture2DLodZeroDepthCompare: + case TextureScalarType.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case TextureScalarType.Texture2DLodLevelDepthCompare: + case TextureScalarType.TextureCubeLodLevel: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + break; + } + } + } + else if (op is OpCodeTlds tldsOp) + { + type = GetTextureType (tldsOp.Type); + flags = GetTextureFlags(tldsOp.Type) | TextureFlags.IntCoords; + + switch (tldsOp.Type) + { + case TexelLoadScalarType.Texture1DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadScalarType.Texture1DLodLevel: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexelLoadScalarType.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadScalarType.Texture2DLodZeroOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadScalarType.Texture2DLodZeroMultisample: + case TexelLoadScalarType.Texture2DLodLevel: + case TexelLoadScalarType.Texture2DLodLevelOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexelLoadScalarType.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadScalarType.Texture2DArrayLodZero: + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + } + + if ((flags & TextureFlags.Offset) != 0) + { + AddTextureOffset(type.GetCoordsCount(), 4, 4); + } + } + else if (op is OpCodeTld4s tld4sOp) + { + if (!(tld4sOp.HasDepthCompare || tld4sOp.HasOffset)) + { + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + } + else + { + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + } + + type = TextureType.Texture2D; + flags = TextureFlags.Gather; + + if (tld4sOp.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= TextureType.Shadow; + } + + if (tld4sOp.HasOffset) + { + AddTextureOffset(type.GetCoordsCount(), 8, 6); + + flags |= TextureFlags.Offset; + } + + sourcesList.Add(Const(tld4sOp.GatherCompIndex)); + } + else + { + throw new InvalidOperationException($"Invalid opcode type \"{op.GetType().Name}\"."); + } + + Operand[] sources = sourcesList.ToArray(); + + Operand[] rd0 = new Operand[2] { ConstF(0), ConstF(0) }; + Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) }; + + int destIncrement = 0; + + Operand GetDest() + { + int high = destIncrement >> 1; + int low = destIncrement & 1; + + destIncrement++; + + if (op.IsFp16) + { + return high != 0 + ? (rd1[low] = Local()) + : (rd0[low] = Local()); + } + else + { + int rdIndex = high != 0 ? op.Rd1.Index : op.Rd0.Index; + + if (rdIndex < RegisterConsts.RegisterZeroIndex) + { + rdIndex += low; + } + + return Register(rdIndex, RegisterType.Gpr); + } + } + + int handle = op.Immediate; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + + if (op.IsFp16) + { + context.Copy(Register(op.Rd0), context.PackHalf2x16(rd0[0], rd0[1])); + context.Copy(Register(op.Rd1), context.PackHalf2x16(rd1[0], rd1[1])); + } + } + + public static void Tld4(EmitterContext context) + { + OpCodeTld4 op = (OpCodeTld4)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + Operand arrayIndex = op.IsArray ? Ra() : null; + + List sourcesList = new List(); + + TextureType type = GetTextureType(op.Dimensions); + + TextureFlags flags = TextureFlags.Gather; + + int coordsCount = type.GetCoordsCount(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (op.IsArray) + { + sourcesList.Add(arrayIndex); + + type |= TextureType.Array; + } + + Operand[] packedOffs = new Operand[2]; + + packedOffs[0] = op.Offset != TextureGatherOffset.None ? Rb() : null; + packedOffs[1] = op.Offset == TextureGatherOffset.Offsets ? Rb() : null; + + if (op.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= TextureType.Shadow; + } + + if (op.Offset != TextureGatherOffset.None) + { + int offsetTexelsCount = op.Offset == TextureGatherOffset.Offsets ? 4 : 1; + + for (int index = 0; index < coordsCount * offsetTexelsCount; index++) + { + Operand packed = packedOffs[(index >> 2) & 1]; + + sourcesList.Add(context.BitfieldExtractS32(packed, Const((index & 3) * 8), Const(6))); + } + + flags |= op.Offset == TextureGatherOffset.Offsets + ? TextureFlags.Offsets + : TextureFlags.Offset; + } + + sourcesList.Add(Const(op.GatherCompIndex)); + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = op.Immediate; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + public static void Txq(EmitterContext context) + { + Txq(context, bindless: false); + } + + public static void Txq_B(EmitterContext context) + { + Txq(context, bindless: true); + } + + private static void Txq(EmitterContext context, bool bindless) + { + OpCodeTex op = (OpCodeTex)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + TextureProperty property = (TextureProperty)op.RawOpCode.Extract(22, 6); + + //TODO: Validate and use property. + Instruction inst = Instruction.TextureSize; + + TextureType type = TextureType.Texture2D; + + TextureFlags flags = bindless ? TextureFlags.Bindless : TextureFlags.None; + + int raIndex = op.Ra.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + List sourcesList = new List(); + + if (bindless) + { + sourcesList.Add(Ra()); + } + + sourcesList.Add(Ra()); + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !bindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + inst, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + private static void Tex(EmitterContext context, TextureFlags flags) + { + OpCodeTexture op = (OpCodeTexture)context.CurrOp; + + bool isBindless = (flags & TextureFlags.Bindless) != 0; + bool intCoords = (flags & TextureFlags.IntCoords) != 0; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + Operand arrayIndex = op.IsArray ? Ra() : null; + + List sourcesList = new List(); + + if (isBindless) + { + sourcesList.Add(Rb()); + } + + TextureType type = GetTextureType(op.Dimensions); + + int coordsCount = type.GetCoordsCount(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (op.IsArray) + { + sourcesList.Add(arrayIndex); + + type |= TextureType.Array; + } + + bool hasLod = op.LodMode > TextureLodMode.LodZero; + + Operand lodValue = hasLod ? Rb() : ConstF(0); + + Operand packedOffs = op.HasOffset ? Rb() : null; + + if (op.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= TextureType.Shadow; + } + + if ((op.LodMode == TextureLodMode.LodZero || + op.LodMode == TextureLodMode.LodLevel || + op.LodMode == TextureLodMode.LodLevelA) && !op.IsMultisample) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodLevel; + } + + if (op.HasOffset) + { + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * 4), Const(4))); + } + + flags |= TextureFlags.Offset; + } + + if (op.LodMode == TextureLodMode.LodBias || + op.LodMode == TextureLodMode.LodBiasA) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodBias; + } + + if (op.IsMultisample) + { + sourcesList.Add(Rb()); + + type |= TextureType.Multisample; + } + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !isBindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + private static TextureType GetTextureType(TextureDimensions dimensions) + { + switch (dimensions) + { + case TextureDimensions.Texture1D: return TextureType.Texture1D; + case TextureDimensions.Texture2D: return TextureType.Texture2D; + case TextureDimensions.Texture3D: return TextureType.Texture3D; + case TextureDimensions.TextureCube: return TextureType.TextureCube; + } + + throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\"."); + } + + private static TextureType GetTextureType(TextureScalarType type) + { + switch (type) + { + case TextureScalarType.Texture1DLodZero: + return TextureType.Texture1D; + + case TextureScalarType.Texture2D: + case TextureScalarType.Texture2DLodZero: + case TextureScalarType.Texture2DLodLevel: + return TextureType.Texture2D; + + case TextureScalarType.Texture2DDepthCompare: + case TextureScalarType.Texture2DLodLevelDepthCompare: + case TextureScalarType.Texture2DLodZeroDepthCompare: + return TextureType.Texture2D | TextureType.Shadow; + + case TextureScalarType.Texture2DArray: + case TextureScalarType.Texture2DArrayLodZero: + return TextureType.Texture2D | TextureType.Array; + + case TextureScalarType.Texture2DArrayLodZeroDepthCompare: + return TextureType.Texture2D | TextureType.Array | TextureType.Shadow; + + case TextureScalarType.Texture3D: + case TextureScalarType.Texture3DLodZero: + return TextureType.Texture3D; + + case TextureScalarType.TextureCube: + case TextureScalarType.TextureCubeLodLevel: + return TextureType.TextureCube; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + + private static TextureType GetTextureType(TexelLoadScalarType type) + { + switch (type) + { + case TexelLoadScalarType.Texture1DLodZero: + case TexelLoadScalarType.Texture1DLodLevel: + return TextureType.Texture1D; + + case TexelLoadScalarType.Texture2DLodZero: + case TexelLoadScalarType.Texture2DLodZeroOffset: + case TexelLoadScalarType.Texture2DLodLevel: + case TexelLoadScalarType.Texture2DLodLevelOffset: + return TextureType.Texture2D; + + case TexelLoadScalarType.Texture2DLodZeroMultisample: + return TextureType.Texture2D | TextureType.Multisample; + + case TexelLoadScalarType.Texture3DLodZero: + return TextureType.Texture3D; + + case TexelLoadScalarType.Texture2DArrayLodZero: + return TextureType.Texture2D | TextureType.Array; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + + private static TextureFlags GetTextureFlags(TextureScalarType type) + { + switch (type) + { + case TextureScalarType.Texture1DLodZero: + case TextureScalarType.Texture2DLodZero: + case TextureScalarType.Texture2DLodLevel: + case TextureScalarType.Texture2DLodLevelDepthCompare: + case TextureScalarType.Texture2DLodZeroDepthCompare: + case TextureScalarType.Texture2DArrayLodZero: + case TextureScalarType.Texture2DArrayLodZeroDepthCompare: + case TextureScalarType.Texture3DLodZero: + case TextureScalarType.TextureCubeLodLevel: + return TextureFlags.LodLevel; + + case TextureScalarType.Texture2D: + case TextureScalarType.Texture2DDepthCompare: + case TextureScalarType.Texture2DArray: + case TextureScalarType.Texture3D: + case TextureScalarType.TextureCube: + return TextureFlags.None; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + + private static TextureFlags GetTextureFlags(TexelLoadScalarType type) + { + switch (type) + { + case TexelLoadScalarType.Texture1DLodZero: + case TexelLoadScalarType.Texture1DLodLevel: + case TexelLoadScalarType.Texture2DLodZero: + case TexelLoadScalarType.Texture2DLodLevel: + case TexelLoadScalarType.Texture2DLodZeroMultisample: + case TexelLoadScalarType.Texture3DLodZero: + case TexelLoadScalarType.Texture2DArrayLodZero: + return TextureFlags.LodLevel; + + case TexelLoadScalarType.Texture2DLodZeroOffset: + case TexelLoadScalarType.Texture2DLodLevelOffset: + return TextureFlags.LodLevel | TextureFlags.Offset; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs new file mode 100644 index 000000000..91c740b68 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs @@ -0,0 +1,6 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + delegate void InstEmitter(EmitterContext context); +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs b/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs new file mode 100644 index 000000000..e55ed660a --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs @@ -0,0 +1,149 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class Lop3Expression + { + public static Operand GetFromTruthTable( + EmitterContext context, + Operand srcA, + Operand srcB, + Operand srcC, + int imm) + { + Operand expr = null; + + //Handle some simple cases, or cases where + //the KMap would yield poor results (like XORs). + if (imm == 0x96 || imm == 0x69) + { + //XOR (0x96) and XNOR (0x69). + if (imm == 0x69) + { + srcA = context.BitwiseNot(srcA); + } + + expr = context.BitwiseExclusiveOr(srcA, srcB); + expr = context.BitwiseExclusiveOr(expr, srcC); + + return expr; + } + else if (imm == 0) + { + //Always false. + return Const(IrConsts.False); + } + else if (imm == 0xff) + { + //Always true. + return Const(IrConsts.True); + } + + int map; + + //Encode into gray code. + map = ((imm >> 0) & 1) << 0; + map |= ((imm >> 1) & 1) << 4; + map |= ((imm >> 2) & 1) << 1; + map |= ((imm >> 3) & 1) << 5; + map |= ((imm >> 4) & 1) << 3; + map |= ((imm >> 5) & 1) << 7; + map |= ((imm >> 6) & 1) << 2; + map |= ((imm >> 7) & 1) << 6; + + //Solve KMap, get sum of products. + int visited = 0; + + for (int index = 0; index < 8 && visited != 0xff; index++) + { + if ((map & (1 << index)) == 0) + { + continue; + } + + int mask = 0; + + for (int mSize = 4; mSize != 0; mSize >>= 1) + { + mask = RotateLeft4((1 << mSize) - 1, index & 3) << (index & 4); + + if ((map & mask) == mask) + { + break; + } + } + + //The mask should wrap, if we are on the high row, shift to low etc. + int mask2 = (index & 4) != 0 ? mask >> 4 : mask << 4; + + if ((map & mask2) == mask2) + { + mask |= mask2; + } + + if ((mask & visited) == mask) + { + continue; + } + + bool notA = (mask & 0x33) != 0; + bool notB = (mask & 0x99) != 0; + bool notC = (mask & 0x0f) != 0; + + bool aChanges = (mask & 0xcc) != 0 && notA; + bool bChanges = (mask & 0x66) != 0 && notB; + bool cChanges = (mask & 0xf0) != 0 && notC; + + Operand localExpr = null; + + void And(Operand source) + { + if (localExpr != null) + { + localExpr = context.BitwiseAnd(localExpr, source); + } + else + { + localExpr = source; + } + } + + if (!aChanges) + { + And(context.BitwiseNot(srcA, notA)); + } + + if (!bChanges) + { + And(context.BitwiseNot(srcB, notB)); + } + + if (!cChanges) + { + And(context.BitwiseNot(srcC, notC)); + } + + if (expr != null) + { + expr = context.BitwiseOr(expr, localExpr); + } + else + { + expr = localExpr; + } + + visited |= mask; + } + + return expr; + } + + private static int RotateLeft4(int value, int shift) + { + return ((value << shift) | (value >> (4 - shift))) & 0xf; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 000000000..949753377 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class BasicBlock + { + public int Index { get; set; } + + public LinkedList Operations { get; } + + private BasicBlock _next; + private BasicBlock _branch; + + public BasicBlock Next + { + get => _next; + set => _next = AddSuccessor(_next, value); + } + + public BasicBlock Branch + { + get => _branch; + set => _branch = AddSuccessor(_branch, value); + } + + public bool HasBranch => _branch != null; + + public List Predecessors { get; } + + public HashSet DominanceFrontiers { get; } + + public BasicBlock ImmediateDominator { get; set; } + + public BasicBlock() + { + Operations = new LinkedList(); + + Predecessors = new List(); + + DominanceFrontiers = new HashSet(); + } + + public BasicBlock(int index) : this() + { + Index = index; + } + + private BasicBlock AddSuccessor(BasicBlock oldBlock, BasicBlock newBlock) + { + oldBlock?.Predecessors.Remove(this); + newBlock?.Predecessors.Add(this); + + return newBlock; + } + + public INode GetLastOp() + { + return Operations.Last?.Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/INode.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/INode.cs new file mode 100644 index 000000000..48dda24b1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/INode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + interface INode + { + Operand Dest { get; set; } + + int SourcesCount { get; } + + Operand GetSource(int index); + + void SetSource(int index, Operand operand); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs new file mode 100644 index 000000000..ac0ebc2b0 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,87 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum Instruction + { + Absolute = 1, + Add, + BitfieldExtractS32, + BitfieldExtractU32, + BitfieldInsert, + BitfieldReverse, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + Branch, + BranchIfFalse, + BranchIfTrue, + Ceiling, + Clamp, + ClampU32, + CompareEqual, + CompareGreater, + CompareGreaterOrEqual, + CompareGreaterOrEqualU32, + CompareGreaterU32, + CompareLess, + CompareLessOrEqual, + CompareLessOrEqualU32, + CompareLessU32, + CompareNotEqual, + ConditionalSelect, + ConvertFPToS32, + ConvertS32ToFP, + ConvertU32ToFP, + Copy, + Cosine, + Discard, + Divide, + EmitVertex, + EndPrimitive, + ExponentB2, + Floor, + FusedMultiplyAdd, + IsNan, + LoadConstant, + LoadGlobal, + LoadLocal, + LogarithmB2, + LogicalAnd, + LogicalExclusiveOr, + LogicalNot, + LogicalOr, + LoopBreak, + LoopContinue, + MarkLabel, + Maximum, + MaximumU32, + Minimum, + MinimumU32, + Multiply, + Negate, + PackDouble2x32, + PackHalf2x16, + ReciprocalSquareRoot, + Return, + ShiftLeft, + ShiftRightS32, + ShiftRightU32, + Sine, + SquareRoot, + StoreGlobal, + StoreLocal, + Subtract, + TextureSample, + TextureSize, + Truncate, + UnpackDouble2x32, + UnpackHalf2x16, + + Count, + FP = 1 << 16, + Mask = 0xffff + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs new file mode 100644 index 000000000..c264e47d1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class IrConsts + { + public const int False = 0; + public const int True = -1; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs new file mode 100644 index 000000000..1df88a3d9 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs @@ -0,0 +1,79 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operand + { + private const int CbufSlotBits = 5; + private const int CbufSlotLsb = 32 - CbufSlotBits; + private const int CbufSlotMask = (1 << CbufSlotBits) - 1; + + public OperandType Type { get; } + + public int Value { get; } + + public INode AsgOp { get; set; } + + public HashSet UseOps { get; } + + private Operand() + { + UseOps = new HashSet(); + } + + public Operand(OperandType type) : this() + { + Type = type; + } + + public Operand(OperandType type, int value) : this() + { + Type = type; + Value = value; + } + + public Operand(Register reg) : this() + { + Type = OperandType.Register; + Value = PackRegInfo(reg.Index, reg.Type); + } + + public Operand(int slot, int offset) : this() + { + Type = OperandType.ConstantBuffer; + Value = PackCbufInfo(slot, offset); + } + + private static int PackCbufInfo(int slot, int offset) + { + return (slot << CbufSlotLsb) | offset; + } + + private static int PackRegInfo(int index, RegisterType type) + { + return ((int)type << 24) | index; + } + + public int GetCbufSlot() + { + return (Value >> CbufSlotLsb) & CbufSlotMask; + } + + public int GetCbufOffset() + { + return Value & ~(CbufSlotMask << CbufSlotLsb); + } + + public Register GetRegister() + { + return new Register(Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + public float AsFloat() + { + return BitConverter.Int32BitsToSingle(Value); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs new file mode 100644 index 000000000..6765f8a44 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs @@ -0,0 +1,62 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class OperandHelper + { + public static Operand Attribute(int value) + { + return new Operand(OperandType.Attribute, value); + } + + public static Operand Cbuf(int slot, int offset) + { + return new Operand(slot, offset); + } + + public static Operand Const(int value) + { + return new Operand(OperandType.Constant, value); + } + + public static Operand ConstF(float value) + { + return new Operand(OperandType.Constant, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand Label() + { + return new Operand(OperandType.Label); + } + + public static Operand Local() + { + return new Operand(OperandType.LocalVariable); + } + + public static Operand Register(int index, RegisterType type) + { + return Register(new Register(index, type)); + } + + public static Operand Register(Register reg) + { + if (reg.IsRZ) + { + return Const(0); + } + else if (reg.IsPT) + { + return Const(IrConsts.True); + } + + return new Operand(reg); + } + + public static Operand Undef() + { + return new Operand(OperandType.Undefined); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs new file mode 100644 index 000000000..e0e2a6675 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + enum OperandType + { + Attribute, + Constant, + ConstantBuffer, + GlobalMemory, + Label, + LocalMemory, + LocalVariable, + Register, + Undefined + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs new file mode 100644 index 000000000..f65799537 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs @@ -0,0 +1,101 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operation : INode + { + public Instruction Inst { get; private set; } + + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + private Operand[] _sources; + + public int SourcesCount => _sources.Length; + + public int ComponentIndex { get; } + + public Operation(Instruction inst, Operand dest, params Operand[] sources) + { + Inst = inst; + Dest = dest; + + //The array may be modified externally, so we store a copy. + _sources = (Operand[])sources.Clone(); + + for (int index = 0; index < _sources.Length; index++) + { + Operand source = _sources[index]; + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + } + } + + public Operation( + Instruction inst, + int compIndex, + Operand dest, + params Operand[] sources) : this(inst, dest, sources) + { + ComponentIndex = compIndex; + } + + private Operand AssignDest(Operand dest) + { + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + return dest; + } + + public Operand GetSource(int index) + { + return _sources[index]; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index]; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index] = source; + } + + public void TurnIntoCopy(Operand source) + { + Inst = Instruction.Copy; + + foreach (Operand oldSrc in _sources) + { + if (oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources = new Operand[] { source }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs new file mode 100644 index 000000000..13ff41bd1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class PhiNode : INode + { + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + private HashSet _blocks; + + private class PhiSource + { + public BasicBlock Block { get; } + public Operand Operand { get; set; } + + public PhiSource(BasicBlock block, Operand operand) + { + Block = block; + Operand = operand; + } + } + + private List _sources; + + public int SourcesCount => _sources.Count; + + public PhiNode(Operand dest) + { + _blocks = new HashSet(); + + _sources = new List(); + + dest.AsgOp = this; + + Dest = dest; + } + + private Operand AssignDest(Operand dest) + { + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + return dest; + } + + public void AddSource(BasicBlock block, Operand operand) + { + if (_blocks.Add(block)) + { + if (operand.Type == OperandType.LocalVariable) + { + operand.UseOps.Add(this); + } + + _sources.Add(new PhiSource(block, operand)); + } + } + + public Operand GetSource(int index) + { + return _sources[index].Operand; + } + + public BasicBlock GetBlock(int index) + { + return _sources[index].Block; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index].Operand; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index].Operand = source; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs new file mode 100644 index 000000000..5f0a84276 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum TextureFlags + { + None = 0, + Bindless = 1 << 0, + Gather = 1 << 1, + IntCoords = 1 << 2, + LodBias = 1 << 3, + LodLevel = 1 << 4, + Offset = 1 << 5, + Offsets = 1 << 6 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs new file mode 100644 index 000000000..f5f2cc5c6 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class TextureOperation : Operation + { + public TextureType Type { get; } + public TextureFlags Flags { get; } + + public int Handle { get; } + + public TextureOperation( + Instruction inst, + TextureType type, + TextureFlags flags, + int handle, + int compIndex, + Operand dest, + params Operand[] sources) : base(inst, compIndex, dest, sources) + { + Type = type; + Flags = flags; + Handle = handle; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs new file mode 100644 index 000000000..bf2070077 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs @@ -0,0 +1,35 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum TextureType + { + Texture1D, + Texture2D, + Texture3D, + TextureCube, + + Mask = 0xff, + + Array = 1 << 8, + Multisample = 1 << 9, + Shadow = 1 << 10 + } + + static class TextureTypeExtensions + { + public static int GetCoordsCount(this TextureType type) + { + switch (type & TextureType.Mask) + { + case TextureType.Texture1D: return 1; + case TextureType.Texture2D: return 2; + case TextureType.Texture3D: return 3; + case TextureType.TextureCube: return 3; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderConfig.cs b/Ryujinx.Graphics/Shader/ShaderConfig.cs new file mode 100644 index 000000000..c2a94814e --- /dev/null +++ b/Ryujinx.Graphics/Shader/ShaderConfig.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Gal; +using System; + +namespace Ryujinx.Graphics.Shader +{ + public struct ShaderConfig + { + public GalShaderType Type { get; } + + public int MaxCBufferSize; + + public ShaderConfig(GalShaderType type, int maxCBufferSize) + { + if (maxCBufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxCBufferSize)); + } + + Type = type; + MaxCBufferSize = maxCBufferSize; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderHeader.cs b/Ryujinx.Graphics/Shader/ShaderHeader.cs new file mode 100644 index 000000000..53abdc56e --- /dev/null +++ b/Ryujinx.Graphics/Shader/ShaderHeader.cs @@ -0,0 +1,166 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.Decoders; +using System; + +namespace Ryujinx.Graphics.Shader +{ + struct OutputMapTarget + { + public bool Red { get; } + public bool Green { get; } + public bool Blue { get; } + public bool Alpha { get; } + + public bool Enabled => Red || Green || Blue || Alpha; + + public OutputMapTarget(bool red, bool green, bool blue, bool alpha) + { + Red = red; + Green = green; + Blue = blue; + Alpha = alpha; + } + + public bool ComponentEnabled(int component) + { + switch (component) + { + case 0: return Red; + case 1: return Green; + case 2: return Blue; + case 3: return Alpha; + } + + throw new ArgumentOutOfRangeException(nameof(component)); + } + } + + class ShaderHeader + { + public int SphType { get; } + + public int Version { get; } + + public int ShaderType { get; } + + public bool MrtEnable { get; } + + public bool KillsPixels { get; } + + public bool DoesGlobalStore { get; } + + public int SassVersion { get; } + + public bool DoesLoadOrStore { get; } + + public bool DoesFp64 { get; } + + public int StreamOutMask{ get; } + + public int ShaderLocalMemoryLowSize { get; } + + public int PerPatchAttributeCount { get; } + + public int ShaderLocalMemoryHighSize { get; } + + public int ThreadsPerInputPrimitive { get; } + + public int ShaderLocalMemoryCrsSize { get; } + + public int OutputTopology { get; } + + public int MaxOutputVertexCount { get; } + + public int StoreReqStart { get; } + public int StoreReqEnd { get; } + + public OutputMapTarget[] OmapTargets { get; } + public bool OmapSampleMask { get; } + public bool OmapDepth { get; } + + public ShaderHeader(IGalMemory memory, ulong address) + { + int commonWord0 = memory.ReadInt32((long)address + 0); + int commonWord1 = memory.ReadInt32((long)address + 4); + int commonWord2 = memory.ReadInt32((long)address + 8); + int commonWord3 = memory.ReadInt32((long)address + 12); + int commonWord4 = memory.ReadInt32((long)address + 16); + + SphType = commonWord0.Extract(0, 5); + + Version = commonWord0.Extract(5, 5); + + ShaderType = commonWord0.Extract(10, 4); + + MrtEnable = commonWord0.Extract(14); + + KillsPixels = commonWord0.Extract(15); + + DoesGlobalStore = commonWord0.Extract(16); + + SassVersion = commonWord0.Extract(17, 4); + + DoesLoadOrStore = commonWord0.Extract(26); + + DoesFp64 = commonWord0.Extract(27); + + StreamOutMask = commonWord0.Extract(28, 4); + + ShaderLocalMemoryLowSize = commonWord1.Extract(0, 24); + + PerPatchAttributeCount = commonWord1.Extract(24, 8); + + ShaderLocalMemoryHighSize = commonWord2.Extract(0, 24); + + ThreadsPerInputPrimitive = commonWord2.Extract(24, 8); + + ShaderLocalMemoryCrsSize = commonWord3.Extract(0, 24); + + OutputTopology = commonWord3.Extract(24, 4); + + MaxOutputVertexCount = commonWord4.Extract(0, 12); + + StoreReqStart = commonWord4.Extract(12, 8); + StoreReqEnd = commonWord4.Extract(24, 8); + + int type2OmapTarget = memory.ReadInt32((long)address + 72); + int type2Omap = memory.ReadInt32((long)address + 76); + + OmapTargets = new OutputMapTarget[8]; + + for (int offset = 0; offset < OmapTargets.Length * 4; offset += 4) + { + OmapTargets[offset >> 2] = new OutputMapTarget( + type2OmapTarget.Extract(offset + 0), + type2OmapTarget.Extract(offset + 1), + type2OmapTarget.Extract(offset + 2), + type2OmapTarget.Extract(offset + 3)); + } + + OmapSampleMask = type2Omap.Extract(0); + OmapDepth = type2Omap.Extract(1); + } + + public int DepthRegister + { + get + { + int count = 0; + + for (int index = 0; index < OmapTargets.Length; index++) + { + for (int component = 0; component < 4; component++) + { + if (OmapTargets[index].ComponentEnabled(component)) + { + count++; + } + } + } + + //Depth register is always two registers after the last color output. + return count + 1; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderProgram.cs b/Ryujinx.Graphics/Shader/ShaderProgram.cs new file mode 100644 index 000000000..9257fd262 --- /dev/null +++ b/Ryujinx.Graphics/Shader/ShaderProgram.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgram + { + public ShaderProgramInfo Info { get; } + + public string Code { get; } + + internal ShaderProgram(ShaderProgramInfo info, string code) + { + Info = info; + Code = code; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs new file mode 100644 index 000000000..c529a3536 --- /dev/null +++ b/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgramInfo + { + public ReadOnlyCollection CBuffers { get; } + public ReadOnlyCollection Textures { get; } + + internal ShaderProgramInfo(CBufferDescriptor[] cBuffers, TextureDescriptor[] textures) + { + CBuffers = Array.AsReadOnly(cBuffers); + Textures = Array.AsReadOnly(textures); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs new file mode 100644 index 000000000..bb3fe7af4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs @@ -0,0 +1,35 @@ +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstAssignment : AstNode + { + public IAstNode Destination { get; } + + private IAstNode _source; + + public IAstNode Source + { + get + { + return _source; + } + set + { + RemoveUse(_source, this); + + AddUse(value, this); + + _source = value; + } + } + + public AstAssignment(IAstNode destination, IAstNode source) + { + Destination = destination; + Source = source; + + AddDef(destination, this); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs new file mode 100644 index 000000000..fdef87de5 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs @@ -0,0 +1,116 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlock : AstNode, IEnumerable + { + public AstBlockType Type { get; private set; } + + private IAstNode _condition; + + public IAstNode Condition + { + get + { + return _condition; + } + set + { + RemoveUse(_condition, this); + + AddUse(value, this); + + _condition = value; + } + } + + private LinkedList _nodes; + + public IAstNode First => _nodes.First?.Value; + + public int Count => _nodes.Count; + + public AstBlock(AstBlockType type, IAstNode condition = null) + { + Type = type; + Condition = condition; + + _nodes = new LinkedList(); + } + + public void Add(IAstNode node) + { + Add(node, _nodes.AddLast(node)); + } + + public void AddFirst(IAstNode node) + { + Add(node, _nodes.AddFirst(node)); + } + + public void AddBefore(IAstNode next, IAstNode node) + { + Add(node, _nodes.AddBefore(next.LLNode, node)); + } + + public void AddAfter(IAstNode prev, IAstNode node) + { + Add(node, _nodes.AddAfter(prev.LLNode, node)); + } + + private void Add(IAstNode node, LinkedListNode newNode) + { + if (node.Parent != null) + { + throw new ArgumentException("Node already belongs to a block."); + } + + node.Parent = this; + node.LLNode = newNode; + } + + public void Remove(IAstNode node) + { + _nodes.Remove(node.LLNode); + + node.Parent = null; + node.LLNode = null; + } + + public void AndCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalAnd, Condition, cond); + } + + public void OrCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalOr, Condition, cond); + } + public void TurnIntoIf(IAstNode cond) + { + Condition = cond; + + Type = AstBlockType.If; + } + + public void TurnIntoElseIf() + { + Type = AstBlockType.ElseIf; + } + + public IEnumerator GetEnumerator() + { + return _nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs new file mode 100644 index 000000000..c12efda90 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum AstBlockType + { + DoWhile, + If, + Else, + ElseIf, + Main, + While + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs new file mode 100644 index 000000000..9397fdb91 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlockVisitor + { + public AstBlock Block { get; private set; } + + public class BlockVisitationEventArgs : EventArgs + { + public AstBlock Block { get; } + + public BlockVisitationEventArgs(AstBlock block) + { + Block = block; + } + } + + public event EventHandler BlockEntered; + public event EventHandler BlockLeft; + + public AstBlockVisitor(AstBlock mainBlock) + { + Block = mainBlock; + } + + public IEnumerable Visit() + { + IAstNode node = Block.First; + + while (node != null) + { + //We reached a child block, visit the nodes inside. + while (node is AstBlock childBlock) + { + Block = childBlock; + + node = childBlock.First; + + BlockEntered?.Invoke(this, new BlockVisitationEventArgs(Block)); + } + + //Node may be null, if the block is empty. + if (node != null) + { + IAstNode next = Next(node); + + yield return node; + + node = next; + } + + //We reached the end of the list, go up on tree to the parent blocks. + while (node == null && Block.Type != AstBlockType.Main) + { + BlockLeft?.Invoke(this, new BlockVisitationEventArgs(Block)); + + node = Next(Block); + + Block = Block.Parent; + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs new file mode 100644 index 000000000..9d3148e1b --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs @@ -0,0 +1,73 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstHelper + { + public static void AddUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Add(parent); + } + } + + public static void AddDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Add(parent); + } + } + + public static void RemoveUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Remove(parent); + } + } + + public static void RemoveDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Remove(parent); + } + } + + public static AstAssignment Assign(IAstNode destination, IAstNode source) + { + return new AstAssignment(destination, source); + } + + public static AstOperand Const(int value) + { + return new AstOperand(OperandType.Constant, value); + } + + public static AstOperand Local(VariableType type) + { + AstOperand local = new AstOperand(OperandType.LocalVariable); + + local.VarType = type; + + return local; + } + + public static IAstNode InverseCond(IAstNode cond) + { + return new AstOperation(Instruction.LogicalNot, cond); + } + + public static IAstNode Next(IAstNode node) + { + return node.LLNode.Next?.Value; + } + + public static IAstNode Previous(IAstNode node) + { + return node.LLNode.Previous?.Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs new file mode 100644 index 000000000..c667aac98 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstNode : IAstNode + { + public AstBlock Parent { get; set; } + + public LinkedListNode LLNode { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs new file mode 100644 index 000000000..97ff3ca97 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperand : AstNode + { + public HashSet Defs { get; } + public HashSet Uses { get; } + + public OperandType Type { get; } + + public VariableType VarType { get; set; } + + public int Value { get; } + + public int CbufSlot { get; } + public int CbufOffset { get; } + + private AstOperand() + { + Defs = new HashSet(); + Uses = new HashSet(); + + VarType = VariableType.S32; + } + + public AstOperand(Operand operand) : this() + { + Type = operand.Type; + + if (Type == OperandType.ConstantBuffer) + { + CbufSlot = operand.GetCbufSlot(); + CbufOffset = operand.GetCbufOffset(); + } + else + { + Value = operand.Value; + } + } + + public AstOperand(OperandType type, int value = 0) : this() + { + Type = type; + Value = value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs new file mode 100644 index 000000000..1607ffecd --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperation : AstNode + { + public Instruction Inst { get; } + + public int ComponentMask { get; } + + private IAstNode[] _sources; + + public int SourcesCount => _sources.Length; + + public AstOperation(Instruction inst, params IAstNode[] sources) + { + Inst = inst; + _sources = sources; + + foreach (IAstNode source in sources) + { + AddUse(source, this); + } + + ComponentMask = 1; + } + + public AstOperation(Instruction inst, int compMask, params IAstNode[] sources) : this(inst, sources) + { + ComponentMask = compMask; + } + + public IAstNode GetSource(int index) + { + return _sources[index]; + } + + public void SetSource(int index, IAstNode source) + { + RemoveUse(_sources[index], this); + + AddUse(source, this); + + _sources[index] = source; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs new file mode 100644 index 000000000..0f5392b7d --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs @@ -0,0 +1,149 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstOptimizer + { + public static void Optimize(StructuredProgramInfo info) + { + AstBlock mainBlock = info.MainBlock; + + AstBlockVisitor visitor = new AstBlockVisitor(mainBlock); + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar) + { + bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source); + + if (propVar.Defs.Count == 1 && isWorthPropagating) + { + PropagateExpression(propVar, assignment.Source); + } + + if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0) + { + visitor.Block.Remove(assignment); + + info.Locals.Remove(propVar); + } + } + } + + RemoveEmptyBlocks(mainBlock); + } + + private static bool IsWorthPropagating(IAstNode source) + { + if (!(source is AstOperation srcOp)) + { + return false; + } + + if (!InstructionInfo.IsUnary(srcOp.Inst)) + { + return false; + } + + return srcOp.GetSource(0) is AstOperand || srcOp.Inst == Instruction.Copy; + } + + private static void PropagateExpression(AstOperand propVar, IAstNode source) + { + IAstNode[] uses = propVar.Uses.ToArray(); + + foreach (IAstNode useNode in uses) + { + if (useNode is AstBlock useBlock) + { + useBlock.Condition = source; + } + else if (useNode is AstOperation useOperation) + { + for (int srcIndex = 0; srcIndex < useOperation.SourcesCount; srcIndex++) + { + if (useOperation.GetSource(srcIndex) == propVar) + { + useOperation.SetSource(srcIndex, source); + } + } + } + else if (useNode is AstAssignment useAssignment) + { + useAssignment.Source = source; + } + } + } + + private static void RemoveEmptyBlocks(AstBlock mainBlock) + { + Queue pending = new Queue(); + + pending.Enqueue(mainBlock); + + while (pending.TryDequeue(out AstBlock block)) + { + foreach (IAstNode node in block) + { + if (node is AstBlock childBlock) + { + pending.Enqueue(childBlock); + } + } + + AstBlock parent = block.Parent; + + if (parent == null) + { + continue; + } + + AstBlock nextBlock = Next(block) as AstBlock; + + bool hasElse = nextBlock != null && nextBlock.Type == AstBlockType.Else; + + bool isIf = block.Type == AstBlockType.If; + + if (block.Count == 0) + { + if (isIf) + { + if (hasElse) + { + nextBlock.TurnIntoIf(InverseCond(block.Condition)); + } + + parent.Remove(block); + } + else if (block.Type == AstBlockType.Else) + { + parent.Remove(block); + } + } + else if (isIf && parent.Type == AstBlockType.Else && parent.Count == (hasElse ? 2 : 1)) + { + AstBlock parentOfParent = parent.Parent; + + parent.Remove(block); + + parentOfParent.AddAfter(parent, block); + + if (hasElse) + { + parent.Remove(nextBlock); + + parentOfParent.AddAfter(block, nextBlock); + } + + parentOfParent.Remove(parent); + + block.TurnIntoElseIf(); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs new file mode 100644 index 000000000..e40f7b70e --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstTextureOperation : AstOperation + { + public TextureType Type { get; } + public TextureFlags Flags { get; } + + public int Handle { get; } + + public AstTextureOperation( + Instruction inst, + TextureType type, + TextureFlags flags, + int handle, + int compMask, + params IAstNode[] sources) : base(inst, compMask, sources) + { + Type = type; + Flags = flags; + Handle = handle; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs b/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs new file mode 100644 index 000000000..dffc3142f --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs @@ -0,0 +1,459 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class GotoElimination + { + //This is a modified version of the algorithm presented on the paper + //"Taming Control Flow: A Structured Approach to Eliminating Goto Statements". + public static void Eliminate(GotoStatement[] gotos) + { + for (int index = gotos.Length - 1; index >= 0; index--) + { + GotoStatement stmt = gotos[index]; + + AstBlock gBlock = ParentBlock(stmt.Goto); + AstBlock lBlock = ParentBlock(stmt.Label); + + int gLevel = Level(gBlock); + int lLevel = Level(lBlock); + + if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + AstBlock drBlock = gBlock; + + int drLevel = gLevel; + + do + { + drBlock = drBlock.Parent; + + drLevel--; + } + while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel)); + + MoveOutward(stmt, gLevel, drLevel); + + gBlock = drBlock; + gLevel = drLevel; + + if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + //It's possible that the label was enclosed inside an else block, + //in this case we need to update the block and level. + //We also need to set the IsLoop for the case when the label is + //now before the goto, due to the newly introduced else block. + lBlock = ParentBlock(stmt.Label); + + lLevel = Level(lBlock); + + if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel)) + { + stmt.IsLoop = true; + } + } + } + + if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + if (gLevel > lLevel) + { + MoveOutward(stmt, gLevel, lLevel); + } + else + { + if (stmt.IsLoop) + { + Lift(stmt); + } + + MoveInward(stmt); + } + } + + gBlock = ParentBlock(stmt.Goto); + + if (stmt.IsLoop) + { + EncloseDoWhile(stmt, gBlock, stmt.Label); + } + else + { + Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label); + } + + gBlock.Remove(stmt.Goto); + } + } + + private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel) + { + return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel)); + } + + private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel) + { + //If the levels are equal, they can be either siblings or indirectly related. + if (lLevel == rLevel) + { + return false; + } + + IAstNode block; + IAstNode other; + + int blockLvl, otherLvl; + + if (lLevel > rLevel) + { + block = lBlock; + blockLvl = lLevel; + other = rBlock; + otherLvl = rLevel; + } + else /* if (rLevel > lLevel) */ + { + block = rBlock; + blockLvl = rLevel; + other = lBlock; + otherLvl = lLevel; + } + + while (blockLvl >= otherLvl) + { + if (block == other) + { + return true; + } + + block = block.Parent; + + blockLvl--; + } + + return false; + } + + private static void Lift(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + AstBlock loopFirstStmt = path[path.Length - 1]; + + if (loopFirstStmt.Type == AstBlockType.Else) + { + loopFirstStmt = Previous(loopFirstStmt) as AstBlock; + + if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + } + + AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt); + + block.Remove(stmt.Goto); + + newBlock.AddFirst(stmt.Goto); + + stmt.IsLoop = false; + } + + private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel) + { + AstBlock origin = ParentBlock(stmt.Goto); + + AstBlock block = origin; + + //Check if a loop is enclosing the goto, and the block that is + //directly related to the label is above the loop block. + //In that case, we need to introduce a break to get out of the loop. + AstBlock loopBlock = origin; + + int loopLevel = gLevel; + + while (loopLevel > lLevel) + { + AstBlock child = loopBlock; + + loopBlock = loopBlock.Parent; + + loopLevel--; + + if (child.Type == AstBlockType.DoWhile) + { + EncloseSingleInst(stmt, Instruction.LoopBreak); + + block.Remove(stmt.Goto); + + loopBlock.AddAfter(child, stmt.Goto); + + block = loopBlock; + gLevel = loopLevel; + } + } + + //Insert ifs to skip the parts that shouldn't be executed due to the goto. + bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If; + + while (gLevel > lLevel) + { + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto)); + + block.Remove(stmt.Goto); + + AstBlock child = block; + + //We can't move the goto in the middle of a if and a else block, in + //this case we need to move it after the else. + //IsLoop may need to be updated if the label is inside the else, as + //introducing a loop is the only way to ensure the else will be executed. + if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + child = elseBlock; + } + + block = block.Parent; + + block.AddAfter(child, stmt.Goto); + + gLevel--; + + if (tryInsertElse && child == origin) + { + AstBlock lBlock = ParentBlock(stmt.Label); + + IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null; + + AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last); + + if (newBlock != null) + { + block.Remove(stmt.Goto); + + block.AddAfter(newBlock, stmt.Goto); + } + } + } + } + + private static void MoveInward(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + for (int index = path.Length - 1; index >= 0; index--) + { + AstBlock child = path[index]; + AstBlock last = child; + + if (child.Type == AstBlockType.If) + { + //Modify the if condition to allow it to be entered by the goto. + if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition)) + { + child.OrCondition(stmt.Condition); + } + } + else if (child.Type == AstBlockType.Else) + { + //Modify the matching if condition to force the else to be entered by the goto. + if (!(Previous(child) is AstBlock ifBlock) || ifBlock.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + + IAstNode cond = InverseCond(stmt.Condition); + + if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond)) + { + ifBlock.AndCondition(cond); + } + + last = ifBlock; + } + + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last); + + block.Remove(stmt.Goto); + + child.AddFirst(stmt.Goto); + + block = child; + } + } + + private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond) + { + while (node is AstOperation operation && operation.SourcesCount == 2) + { + if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond)) + { + return true; + } + + node = operation.GetSource(0); + } + + return false; + } + + private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first) + { + if (block.Type == AstBlockType.DoWhile && first == block.First) + { + //We only need to insert the continue if we're not at the end of the loop, + //or if our condition is different from the loop condition. + if (Next(stmt.Goto) != null || block.Condition != stmt.Condition) + { + EncloseSingleInst(stmt, Instruction.LoopContinue); + } + + //Modify the do-while condition to allow it to continue. + if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition)) + { + block.OrCondition(stmt.Condition); + } + + return block; + } + + return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto); + } + + private static void EncloseSingleInst(GotoStatement stmt, Instruction inst) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock newBlock = new AstBlock(AstBlockType.If, stmt.Condition); + + block.AddAfter(stmt.Goto, newBlock); + + newBlock.AddFirst(new AstOperation(inst)); + } + + private static AstBlock Enclose( + AstBlock block, + AstBlockType type, + IAstNode cond, + IAstNode first, + IAstNode last = null) + { + if (first == last) + { + return null; + } + + if (type == AstBlockType.If) + { + cond = InverseCond(cond); + } + + //Do a quick check, if we are enclosing a single block, + //and the block type/condition matches the one we're going + //to create, then we don't need a new block, we can just + //return the old one. + bool hasSingleNode = Next(first) == last; + + if (hasSingleNode && BlockMatches(first, type, cond)) + { + return first as AstBlock; + } + + AstBlock newBlock = new AstBlock(type, cond); + + block.AddBefore(first, newBlock); + + while (first != last) + { + IAstNode next = Next(first); + + block.Remove(first); + + newBlock.Add(first); + + first = next; + } + + return newBlock; + } + + private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond) + { + if (!(node is AstBlock block)) + { + return false; + } + + return block.Type == type && IsSameCond(block.Condition, cond); + } + + private static bool IsSameCond(IAstNode lCond, IAstNode rCond) + { + if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot) + { + if (!(rCond is AstOperation rCondOp) || rCondOp.Inst != lCondOp.Inst) + { + return false; + } + + lCond = lCondOp.GetSource(0); + rCond = rCondOp.GetSource(0); + } + + return lCond == rCond; + } + + private static AstBlock ParentBlock(IAstNode node) + { + if (node is AstBlock block) + { + return block.Parent; + } + + while (!(node is AstBlock)) + { + node = node.Parent; + } + + return node as AstBlock; + } + + private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom) + { + AstBlock block = bottom; + + List path = new List(); + + while (block != top) + { + path.Add(block); + + block = block.Parent; + } + + return path.ToArray(); + } + + private static int Level(IAstNode node) + { + int level = 0; + + while (node != null) + { + level++; + + node = node.Parent; + } + + return level; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs b/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs new file mode 100644 index 000000000..25216e55f --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class GotoStatement + { + public AstOperation Goto { get; } + public AstAssignment Label { get; } + + public IAstNode Condition => Label.Destination; + + public bool IsLoop { get; set; } + + public bool IsUnconditional => Goto.Inst == Instruction.Branch; + + public GotoStatement(AstOperation branch, AstAssignment label, bool isLoop) + { + Goto = branch; + Label = label; + IsLoop = isLoop; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs b/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs new file mode 100644 index 000000000..5ececbb5e --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + interface IAstNode + { + AstBlock Parent { get; set; } + + LinkedListNode LLNode { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs new file mode 100644 index 000000000..46a61553b --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs @@ -0,0 +1,142 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class InstructionInfo + { + private struct InstInfo + { + public VariableType DestType { get; } + + public VariableType[] SrcTypes { get; } + + public InstInfo(VariableType destType, params VariableType[] srcTypes) + { + DestType = destType; + SrcTypes = srcTypes; + } + } + + private static InstInfo[] _infoTbl; + + static InstructionInfo() + { + _infoTbl = new InstInfo[(int)Instruction.Count]; + + Add(Instruction.Absolute, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Add, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.BitfieldExtractS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldExtractU32, VariableType.U32, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldInsert, VariableType.Int, VariableType.Int, VariableType.Int, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldReverse, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseAnd, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseExclusiveOr, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseNot, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseOr, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BranchIfTrue, VariableType.None, VariableType.Bool); + Add(Instruction.BranchIfFalse, VariableType.None, VariableType.Bool); + Add(Instruction.Ceiling, VariableType.F32, VariableType.F32, VariableType.F32); + Add(Instruction.Clamp, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ClampU32, VariableType.U32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.CompareEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreater, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreaterOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreaterOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareGreaterU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareLess, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareLessOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareLessOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareLessU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareNotEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ConditionalSelect, VariableType.Scalar, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ConvertFPToS32, VariableType.S32, VariableType.F32); + Add(Instruction.ConvertS32ToFP, VariableType.F32, VariableType.S32); + Add(Instruction.ConvertU32ToFP, VariableType.F32, VariableType.U32); + Add(Instruction.Cosine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Floor, VariableType.F32, VariableType.F32); + Add(Instruction.FusedMultiplyAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.F32); + Add(Instruction.IsNan, VariableType.Bool, VariableType.F32); + Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalNot, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int); + Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int); + Add(Instruction.Maximum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MaximumU32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.Minimum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32); + Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.TextureSample, VariableType.F32); + Add(Instruction.TextureSize, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.Truncate, VariableType.F32, VariableType.F32); + Add(Instruction.UnpackHalf2x16, VariableType.F32, VariableType.U32); + } + + private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes) + { + _infoTbl[(int)inst] = new InstInfo(destType, srcTypes); + } + + public static VariableType GetDestVarType(Instruction inst) + { + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst); + } + + public static VariableType GetSrcVarType(Instruction inst, int index) + { + if (inst == Instruction.TextureSample) + { + return VariableType.F32; + } + + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst); + } + + private static VariableType GetFinalVarType(VariableType type, Instruction inst) + { + if (type == VariableType.Scalar) + { + return (inst & Instruction.FP) != 0 + ? VariableType.F32 + : VariableType.S32; + } + else if (type == VariableType.Int) + { + return VariableType.S32; + } + else if (type == VariableType.None) + { + throw new ArgumentException($"Invalid operand for instruction \"{inst}\"."); + } + + return type; + } + + public static bool IsUnary(Instruction inst) + { + if (inst == Instruction.Copy) + { + return true; + } + else if (inst == Instruction.TextureSample) + { + return false; + } + + return _infoTbl[(int)(inst & Instruction.Mask)].SrcTypes.Length == 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs new file mode 100644 index 000000000..a3a8d1383 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs @@ -0,0 +1,34 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class OperandInfo + { + public static VariableType GetVarType(AstOperand operand) + { + if (operand.Type == OperandType.LocalVariable) + { + return operand.VarType; + } + else + { + return GetVarType(operand.Type); + } + } + + public static VariableType GetVarType(OperandType type) + { + switch (type) + { + case OperandType.Attribute: return VariableType.F32; + case OperandType.Constant: return VariableType.S32; + case OperandType.ConstantBuffer: return VariableType.F32; + case OperandType.GlobalMemory: return VariableType.F32; + case OperandType.Undefined: return VariableType.S32; + } + + throw new ArgumentException($"Invalid operand type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs b/Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs new file mode 100644 index 000000000..53391b626 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs @@ -0,0 +1,74 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class PhiFunctions + { + public static void Remove(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + if (!(node.Value is PhiNode phi)) + { + node = nextNode; + + continue; + } + + for (int index = 0; index < phi.SourcesCount; index++) + { + Operand src = phi.GetSource(index); + + BasicBlock srcBlock = phi.GetBlock(index); + + Operation copyOp = new Operation(Instruction.Copy, phi.Dest, src); + + AddBeforeBranch(srcBlock, copyOp); + } + + block.Operations.Remove(node); + + node = nextNode; + } + } + } + + private static void AddBeforeBranch(BasicBlock block, INode node) + { + INode lastOp = block.GetLastOp(); + + if (lastOp is Operation operation && IsControlFlowInst(operation.Inst)) + { + block.Operations.AddBefore(block.Operations.Last, node); + } + else + { + block.Operations.AddLast(node); + } + } + + private static bool IsControlFlowInst(Instruction inst) + { + switch (inst) + { + case Instruction.Branch: + case Instruction.BranchIfFalse: + case Instruction.BranchIfTrue: + case Instruction.Discard: + case Instruction.Return: + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs new file mode 100644 index 000000000..f65631be7 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs @@ -0,0 +1,254 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class StructuredProgram + { + public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks) + { + PhiFunctions.Remove(blocks); + + StructuredProgramContext context = new StructuredProgramContext(blocks.Length); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + context.EnterBlock(block); + + foreach (INode node in block.Operations) + { + Operation operation = (Operation)node; + + if (IsBranchInst(operation.Inst)) + { + context.LeaveBlock(block, operation); + } + else + { + AddOperation(context, operation); + } + } + } + + GotoElimination.Eliminate(context.GetGotos()); + + AstOptimizer.Optimize(context.Info); + + return context.Info; + } + + private static void AddOperation(StructuredProgramContext context, Operation operation) + { + Instruction inst = operation.Inst; + + IAstNode[] sources = new IAstNode[operation.SourcesCount]; + + for (int index = 0; index < sources.Length; index++) + { + sources[index] = context.GetOperandUse(operation.GetSource(index)); + } + + if (operation.Dest != null) + { + AstOperand dest = context.GetOperandDef(operation.Dest); + + if (inst == Instruction.LoadConstant) + { + Operand ldcSource = operation.GetSource(0); + + if (ldcSource.Type != OperandType.Constant) + { + throw new InvalidOperationException("Found LDC with non-constant constant buffer slot."); + } + + context.Info.CBuffers.Add(ldcSource.Value); + } + + AstAssignment assignment; + + //If all the sources are bool, it's better to use short-circuiting + //logical operations, rather than forcing a cast to int and doing + //a bitwise operation with the value, as it is likely to be used as + //a bool in the end. + if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, VariableType.Bool)) + { + inst = GetLogicalFromBitwiseInst(inst); + } + + bool isCondSel = inst == Instruction.ConditionalSelect; + bool isCopy = inst == Instruction.Copy; + + if (isCondSel || isCopy) + { + VariableType type = GetVarTypeFromUses(operation.Dest); + + if (isCondSel && type == VariableType.F32) + { + inst |= Instruction.FP; + } + + dest.VarType = type; + } + else + { + dest.VarType = InstructionInfo.GetDestVarType(inst); + } + + int componentMask = 1 << operation.ComponentIndex; + + IAstNode source; + + if (operation is TextureOperation texOp) + { + AstTextureOperation astTexOp = new AstTextureOperation( + inst, + texOp.Type, + texOp.Flags, + texOp.Handle, + componentMask, + sources); + + context.Info.Samplers.Add(astTexOp); + + source = astTexOp; + } + else if (!isCopy) + { + source = new AstOperation(inst, componentMask, sources); + } + else + { + source = sources[0]; + } + + assignment = new AstAssignment(dest, source); + + context.AddNode(assignment); + } + else + { + context.AddNode(new AstOperation(inst, sources)); + } + } + + private static VariableType GetVarTypeFromUses(Operand dest) + { + HashSet visited = new HashSet(); + + Queue pending = new Queue(); + + bool Enqueue(Operand operand) + { + if (visited.Add(operand)) + { + pending.Enqueue(operand); + + return true; + } + + return false; + } + + Enqueue(dest); + + while (pending.TryDequeue(out Operand operand)) + { + foreach (INode useNode in operand.UseOps) + { + if (!(useNode is Operation operation)) + { + continue; + } + + if (operation.Inst == Instruction.Copy) + { + if (operation.Dest.Type == OperandType.LocalVariable) + { + if (Enqueue(operation.Dest)) + { + break; + } + } + else + { + return OperandInfo.GetVarType(operation.Dest.Type); + } + } + else + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index) == operand) + { + return InstructionInfo.GetSrcVarType(operation.Inst, index); + } + } + } + } + } + + return VariableType.S32; + } + + private static bool AreAllSourceTypesEqual(IAstNode[] sources, VariableType type) + { + foreach (IAstNode node in sources) + { + if (!(node is AstOperand operand)) + { + return false; + } + + if (operand.VarType != type) + { + return false; + } + } + + return true; + } + + private static bool IsBranchInst(Instruction inst) + { + switch (inst) + { + case Instruction.Branch: + case Instruction.BranchIfFalse: + case Instruction.BranchIfTrue: + return true; + } + + return false; + } + + private static bool IsBitwiseInst(Instruction inst) + { + switch (inst) + { + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + return true; + } + + return false; + } + + private static Instruction GetLogicalFromBitwiseInst(Instruction inst) + { + switch (inst) + { + case Instruction.BitwiseAnd: return Instruction.LogicalAnd; + case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr; + case Instruction.BitwiseNot: return Instruction.LogicalNot; + case Instruction.BitwiseOr: return Instruction.LogicalOr; + } + + throw new ArgumentException($"Unexpected instruction \"{inst}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs new file mode 100644 index 000000000..e1f0503a5 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs @@ -0,0 +1,292 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramContext + { + private HashSet _loopTails; + + private Stack<(AstBlock Block, int EndIndex)> _blockStack; + + private Dictionary _localsMap; + + private Dictionary _gotoTempAsgs; + + private List _gotos; + + private AstBlock _currBlock; + + private int _currEndIndex; + + public StructuredProgramInfo Info { get; } + + public StructuredProgramContext(int blocksCount) + { + _loopTails = new HashSet(); + + _blockStack = new Stack<(AstBlock, int)>(); + + _localsMap = new Dictionary(); + + _gotoTempAsgs = new Dictionary(); + + _gotos = new List(); + + _currBlock = new AstBlock(AstBlockType.Main); + + _currEndIndex = blocksCount; + + Info = new StructuredProgramInfo(_currBlock); + } + + public void EnterBlock(BasicBlock block) + { + while (_currEndIndex == block.Index) + { + (_currBlock, _currEndIndex) = _blockStack.Pop(); + } + + if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg)) + { + AddGotoTempReset(block, gotoTempAsg); + } + + LookForDoWhileStatements(block); + } + + public void LeaveBlock(BasicBlock block, Operation branchOp) + { + LookForIfStatements(block, branchOp); + } + + private void LookForDoWhileStatements(BasicBlock block) + { + //Check if we have any predecessor whose index is greater than the + //current block, this indicates a loop. + bool done = false; + + foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index)) + { + if (predecessor.Index < block.Index) + { + break; + } + + if (predecessor.Index < _currEndIndex && !done) + { + Operation branchOp = (Operation)predecessor.GetLastOp(); + + NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1); + + _loopTails.Add(predecessor); + + done = true; + } + else + { + AddGotoTempReset(block, GetGotoTempAsg(block.Index)); + + break; + } + } + } + + private void LookForIfStatements(BasicBlock block, Operation branchOp) + { + if (block.Branch == null) + { + return; + } + + bool isLoop = block.Branch.Index <= block.Index; + + if (block.Branch.Index <= _currEndIndex && !isLoop) + { + NewBlock(AstBlockType.If, branchOp, block.Branch.Index); + } + else if (!_loopTails.Contains(block)) + { + AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index); + + IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp); + + AddNode(Assign(gotoTempAsg.Destination, cond)); + + AstOperation branch = new AstOperation(branchOp.Inst); + + AddNode(branch); + + GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop); + + _gotos.Add(gotoStmt); + } + } + + private AstAssignment GetGotoTempAsg(int index) + { + if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg)) + { + return gotoTempAsg; + } + + AstOperand gotoTemp = NewTemp(VariableType.Bool); + + gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False)); + + _gotoTempAsgs.Add(index, gotoTempAsg); + + return gotoTempAsg; + } + + private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg) + { + AddNode(gotoTempAsg); + + //For block 0, we don't need to add the extra "reset" at the beggining, + //because it is already the first node to be executed on the shader, + //so it is reset to false by the "local" assignment anyway. + if (block.Index != 0) + { + Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False))); + } + } + + private void NewBlock(AstBlockType type, Operation branchOp, int endIndex) + { + NewBlock(type, GetBranchCond(type, branchOp), endIndex); + } + + private void NewBlock(AstBlockType type, IAstNode cond, int endIndex) + { + AstBlock childBlock = new AstBlock(type, cond); + + AddNode(childBlock); + + _blockStack.Push((_currBlock, _currEndIndex)); + + _currBlock = childBlock; + _currEndIndex = endIndex; + } + + private IAstNode GetBranchCond(AstBlockType type, Operation branchOp) + { + IAstNode cond; + + if (branchOp.Inst == Instruction.Branch) + { + cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True); + } + else + { + cond = GetOperandUse(branchOp.GetSource(0)); + + Instruction invInst = type == AstBlockType.If + ? Instruction.BranchIfTrue + : Instruction.BranchIfFalse; + + if (branchOp.Inst == invInst) + { + cond = new AstOperation(Instruction.LogicalNot, cond); + } + } + + return cond; + } + + public void AddNode(IAstNode node) + { + _currBlock.Add(node); + } + + public GotoStatement[] GetGotos() + { + return _gotos.ToArray(); + } + + private AstOperand NewTemp(VariableType type) + { + AstOperand newTemp = Local(type); + + Info.Locals.Add(newTemp); + + return newTemp; + } + + public AstOperand GetOperandDef(Operand operand) + { + if (TryGetUserAttributeIndex(operand, out int attrIndex)) + { + Info.OAttributes.Add(attrIndex); + } + + return GetOperand(operand); + } + + public AstOperand GetOperandUse(Operand operand) + { + if (TryGetUserAttributeIndex(operand, out int attrIndex)) + { + Info.IAttributes.Add(attrIndex); + } + else if (operand.Type == OperandType.ConstantBuffer) + { + Info.CBuffers.Add(operand.GetCbufSlot()); + } + + return GetOperand(operand); + } + + private AstOperand GetOperand(Operand operand) + { + if (operand == null) + { + return null; + } + + if (operand.Type != OperandType.LocalVariable) + { + return new AstOperand(operand); + } + + if (!_localsMap.TryGetValue(operand, out AstOperand astOperand)) + { + astOperand = new AstOperand(operand); + + _localsMap.Add(operand, astOperand); + + Info.Locals.Add(astOperand); + } + + return astOperand; + } + + private static bool TryGetUserAttributeIndex(Operand operand, out int attrIndex) + { + if (operand.Type == OperandType.Attribute) + { + if (operand.Value >= AttributeConsts.UserAttributeBase && + operand.Value < AttributeConsts.UserAttributeEnd) + { + attrIndex = (operand.Value - AttributeConsts.UserAttributeBase) >> 4; + + return true; + } + else if (operand.Value >= AttributeConsts.FragmentOutputColorBase && + operand.Value < AttributeConsts.FragmentOutputColorEnd) + { + attrIndex = (operand.Value - AttributeConsts.FragmentOutputColorBase) >> 4; + + return true; + } + } + + attrIndex = 0; + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs new file mode 100644 index 000000000..d368ef005 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramInfo + { + public AstBlock MainBlock { get; } + + public HashSet Locals { get; } + + public HashSet CBuffers { get; } + + public HashSet IAttributes { get; } + public HashSet OAttributes { get; } + + public HashSet Samplers { get; } + + public StructuredProgramInfo(AstBlock mainBlock) + { + MainBlock = mainBlock; + + Locals = new HashSet(); + + CBuffers = new HashSet(); + + IAttributes = new HashSet(); + OAttributes = new HashSet(); + + Samplers = new HashSet(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs b/Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs new file mode 100644 index 000000000..4c7f38497 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum VariableType + { + None, + Bool, + Scalar, + Int, + F32, + S32, + U32 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/TextureDescriptor.cs b/Ryujinx.Graphics/Shader/TextureDescriptor.cs new file mode 100644 index 000000000..96f0f5b16 --- /dev/null +++ b/Ryujinx.Graphics/Shader/TextureDescriptor.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.Graphics.Shader +{ + public struct TextureDescriptor + { + public string Name { get; } + + public int HandleIndex { get; } + + public bool IsBindless { get; } + + public int CbufSlot { get; } + public int CbufOffset { get; } + + public TextureDescriptor(string name, int hIndex) + { + Name = name; + HandleIndex = hIndex; + + IsBindless = false; + + CbufSlot = 0; + CbufOffset = 0; + } + + public TextureDescriptor(string name, int cbufSlot, int cbufOffset) + { + Name = name; + HandleIndex = 0; + + IsBindless = true; + + CbufSlot = cbufSlot; + CbufOffset = cbufOffset; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs new file mode 100644 index 000000000..ae3e361c7 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class AttributeConsts + { + public const int Layer = 0x064; + public const int PointSize = 0x06c; + public const int PositionX = 0x070; + public const int PositionY = 0x074; + public const int PositionZ = 0x078; + public const int PositionW = 0x07c; + public const int PointCoordX = 0x2e0; + public const int PointCoordY = 0x2e4; + public const int TessCoordX = 0x2f0; + public const int TessCoordY = 0x2f4; + public const int InstanceId = 0x2f8; + public const int VertexId = 0x2fc; + public const int FrontFacing = 0x3fc; + + public const int UserAttributesCount = 32; + public const int UserAttributeBase = 0x80; + public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16; + + + //Note: Those attributes are used internally by the translator + //only, they don't exist on Maxwell. + public const int FragmentOutputDepth = 0x1000000; + public const int FragmentOutputColorBase = 0x1000010; + public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs b/Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs new file mode 100644 index 000000000..e2ca74a4d --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs @@ -0,0 +1,108 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class ControlFlowGraph + { + public static BasicBlock[] MakeCfg(Operation[] operations) + { + Dictionary labels = new Dictionary(); + + List blocks = new List(); + + BasicBlock currentBlock = null; + + void NextBlock(BasicBlock nextBlock) + { + if (currentBlock != null && !EndsWithUnconditionalInst(currentBlock.GetLastOp())) + { + currentBlock.Next = nextBlock; + } + + currentBlock = nextBlock; + } + + void NewNextBlock() + { + BasicBlock block = new BasicBlock(blocks.Count); + + blocks.Add(block); + + NextBlock(block); + } + + bool needsNewBlock = true; + + for (int index = 0; index < operations.Length; index++) + { + Operation operation = operations[index]; + + if (operation.Inst == Instruction.MarkLabel) + { + Operand label = operation.Dest; + + if (labels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = blocks.Count; + + blocks.Add(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + labels.Add(label, currentBlock); + } + } + else + { + if (needsNewBlock) + { + NewNextBlock(); + } + + currentBlock.Operations.AddLast(operation); + } + + needsNewBlock = operation.Inst == Instruction.Branch || + operation.Inst == Instruction.BranchIfTrue || + operation.Inst == Instruction.BranchIfFalse; + + if (needsNewBlock) + { + Operand label = operation.Dest; + + if (!labels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + labels.Add(label, branchBlock); + } + + currentBlock.Branch = branchBlock; + } + } + + return blocks.ToArray(); + } + + private static bool EndsWithUnconditionalInst(INode node) + { + if (node is Operation operation) + { + switch (operation.Inst) + { + case Instruction.Branch: + case Instruction.Discard: + case Instruction.Return: + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Dominance.cs b/Ryujinx.Graphics/Shader/Translation/Dominance.cs new file mode 100644 index 000000000..b4b80e3ef --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Dominance.cs @@ -0,0 +1,127 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Dominance + { + //Those methods are an implementation of the algorithms on "A Simple, Fast Dominance Algorithm". + //https://www.cs.rice.edu/~keith/EMBED/dom.pdf + public static void FindDominators(BasicBlock entry, int blocksCount) + { + HashSet visited = new HashSet(); + + Stack blockStack = new Stack(); + + List postOrderBlocks = new List(blocksCount); + + int[] postOrderMap = new int[blocksCount]; + + visited.Add(entry); + + blockStack.Push(entry); + + while (blockStack.TryPop(out BasicBlock block)) + { + if (block.Next != null && visited.Add(block.Next)) + { + blockStack.Push(block); + blockStack.Push(block.Next); + } + else if (block.Branch != null && visited.Add(block.Branch)) + { + blockStack.Push(block); + blockStack.Push(block.Branch); + } + else + { + postOrderMap[block.Index] = postOrderBlocks.Count; + + postOrderBlocks.Add(block); + } + } + + BasicBlock Intersect(BasicBlock block1, BasicBlock block2) + { + while (block1 != block2) + { + while (postOrderMap[block1.Index] < postOrderMap[block2.Index]) + { + block1 = block1.ImmediateDominator; + } + + while (postOrderMap[block2.Index] < postOrderMap[block1.Index]) + { + block2 = block2.ImmediateDominator; + } + } + + return block1; + } + + entry.ImmediateDominator = entry; + + bool modified; + + do + { + modified = false; + + for (int blkIndex = postOrderBlocks.Count - 2; blkIndex >= 0; blkIndex--) + { + BasicBlock block = postOrderBlocks[blkIndex]; + + BasicBlock newIDom = null; + + foreach (BasicBlock predecessor in block.Predecessors) + { + if (predecessor.ImmediateDominator != null) + { + if (newIDom != null) + { + newIDom = Intersect(predecessor, newIDom); + } + else + { + newIDom = predecessor; + } + } + } + + if (block.ImmediateDominator != newIDom) + { + block.ImmediateDominator = newIDom; + + modified = true; + } + } + } + while (modified); + } + + public static void FindDominanceFrontiers(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + if (block.Predecessors.Count < 2) + { + continue; + } + + for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++) + { + BasicBlock current = block.Predecessors[pBlkIndex]; + + while (current != block.ImmediateDominator) + { + current.DominanceFrontiers.Add(block); + + current = current.ImmediateDominator; + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs new file mode 100644 index 000000000..6c2bf6e47 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs @@ -0,0 +1,105 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class EmitterContext + { + public Block CurrBlock { get; set; } + public OpCode CurrOp { get; set; } + + private GalShaderType _shaderType; + + private ShaderHeader _header; + + private List _operations; + + private Dictionary _labels; + + public EmitterContext(GalShaderType shaderType, ShaderHeader header) + { + _shaderType = shaderType; + _header = header; + + _operations = new List(); + + _labels = new Dictionary(); + } + + public Operand Add(Instruction inst, Operand dest = null, params Operand[] sources) + { + Operation operation = new Operation(inst, dest, sources); + + Add(operation); + + return dest; + } + + public void Add(Operation operation) + { + _operations.Add(operation); + } + + public void MarkLabel(Operand label) + { + Add(Instruction.MarkLabel, label); + } + + public Operand GetLabel(ulong address) + { + if (!_labels.TryGetValue(address, out Operand label)) + { + label = Label(); + + _labels.Add(address, label); + } + + return label; + } + + public void PrepareForReturn() + { + if (_shaderType == GalShaderType.Fragment) + { + if (_header.OmapDepth) + { + Operand dest = Attribute(AttributeConsts.FragmentOutputDepth); + + Operand src = Register(_header.DepthRegister, RegisterType.Gpr); + + this.Copy(dest, src); + } + + int regIndex = 0; + + for (int attachment = 0; attachment < 8; attachment++) + { + OutputMapTarget target = _header.OmapTargets[attachment]; + + for (int component = 0; component < 4; component++) + { + if (target.ComponentEnabled(component)) + { + Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + regIndex * 4); + + Operand src = Register(regIndex, RegisterType.Gpr); + + this.Copy(dest, src); + + regIndex++; + } + } + } + } + } + + public Operation[] GetOperations() + { + return _operations.ToArray(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs new file mode 100644 index 000000000..604aa67d3 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs @@ -0,0 +1,420 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class EmitterContextInsts + { + public static Operand BitfieldExtractS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractS32, Local(), a, b, c); + } + + public static Operand BitfieldExtractU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractU32, Local(), a, b, c); + } + + public static Operand BitfieldInsert(this EmitterContext context, Operand a, Operand b, Operand c, Operand d) + { + return context.Add(Instruction.BitfieldInsert, Local(), a, b, c, d); + } + + public static Operand BitfieldReverse(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitfieldReverse, Local(), a); + } + + public static Operand BitwiseAnd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseAnd, Local(), a, b); + } + + public static Operand BitwiseExclusiveOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseExclusiveOr, Local(), a, b); + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a, bool invert) + { + if (invert) + { + a = context.BitwiseNot(a); + } + + return a; + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitwiseNot, Local(), a); + } + + public static Operand BitwiseOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseOr, Local(), a, b); + } + + public static Operand Branch(this EmitterContext context, Operand d) + { + return context.Add(Instruction.Branch, d); + } + + public static Operand BranchIfFalse(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfFalse, d, a); + } + + public static Operand BranchIfTrue(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfTrue, d, a); + } + + public static Operand ConditionalSelect(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ConditionalSelect, Local(), a, b, c); + } + + public static Operand Copy(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Copy, Local(), a); + } + + public static void Copy(this EmitterContext context, Operand d, Operand a) + { + if (d.Type == OperandType.Constant) + { + return; + } + + context.Add(Instruction.Copy, d, a); + } + + public static Operand Discard(this EmitterContext context) + { + return context.Add(Instruction.Discard); + } + + public static Operand EmitVertex(this EmitterContext context) + { + return context.Add(Instruction.EmitVertex); + } + + public static Operand EndPrimitive(this EmitterContext context) + { + return context.Add(Instruction.EndPrimitive); + } + + public static Operand FPAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) + { + return context.FPNegate(context.FPAbsolute(a, abs), neg); + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a, bool abs) + { + if (abs) + { + a = context.FPAbsolute(a); + } + + return a; + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Absolute, Local(), a); + } + + public static Operand FPAdd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Add, Local(), a, b); + } + + public static Operand FPCeiling(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Ceiling, Local(), a); + } + + public static Operand FPCompareEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.CompareEqual, Local(), a, b); + } + + public static Operand FPCompareLess(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.CompareLess, Local(), a, b); + } + + public static Operand FPConvertToS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFPToS32, Local(), a); + } + + public static Operand FPCosine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Cosine, Local(), a); + } + + public static Operand FPDivide(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Divide, Local(), a, b); + } + + public static Operand FPExponentB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.ExponentB2, Local(), a); + } + + public static Operand FPFloor(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Floor, Local(), a); + } + + public static Operand FPLogarithmB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.LogarithmB2, Local(), a); + } + + public static Operand FPMaximum(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Maximum, Local(), a, b); + } + + public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Minimum, Local(), a, b); + } + + public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Multiply, Local(), a, b); + } + + public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.FusedMultiplyAdd, Local(), a, b, c); + } + + public static Operand FPNegate(this EmitterContext context, Operand a, bool neg) + { + if (neg) + { + a = context.FPNegate(a); + } + + return a; + } + + public static Operand FPNegate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Negate, Local(), a); + } + + public static Operand FPReciprocal(this EmitterContext context, Operand a) + { + return context.FPDivide(ConstF(1), a); + } + + public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.ReciprocalSquareRoot, Local(), a); + } + + public static Operand FPSaturate(this EmitterContext context, Operand a, bool sat) + { + if (sat) + { + a = context.FPSaturate(a); + } + + return a; + } + + public static Operand FPSaturate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1)); + } + + public static Operand FPSine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Sine, Local(), a); + } + + public static Operand FPSquareRoot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.SquareRoot, Local(), a); + } + + public static Operand FPTruncate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Truncate, Local(), a); + } + + public static Operand IAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) + { + return context.INegate(context.IAbsolute(a, abs), neg); + } + + public static Operand IAbsolute(this EmitterContext context, Operand a, bool abs) + { + if (abs) + { + a = context.IAbsolute(a); + } + + return a; + } + + public static Operand IAbsolute(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Absolute, Local(), a); + } + + public static Operand IAdd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Add, Local(), a, b); + } + + public static Operand IClampS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.Clamp, Local(), a, b, c); + } + + public static Operand IClampU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ClampU32, Local(), a, b, c); + } + + public static Operand ICompareEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareEqual, Local(), a, b); + } + + public static Operand ICompareLess(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLess, Local(), a, b); + } + + public static Operand ICompareLessUnsigned(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLessU32, Local(), a, b); + } + + public static Operand ICompareNotEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareNotEqual, Local(), a, b); + } + + public static Operand IConvertS32ToFP(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertS32ToFP, Local(), a); + } + + public static Operand IConvertU32ToFP(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertU32ToFP, Local(), a); + } + + public static Operand IMaximumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Maximum, Local(), a, b); + } + + public static Operand IMaximumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MaximumU32, Local(), a, b); + } + + public static Operand IMinimumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Minimum, Local(), a, b); + } + + public static Operand IMinimumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MinimumU32, Local(), a, b); + } + + public static Operand IMultiply(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Multiply, Local(), a, b); + } + + public static Operand INegate(this EmitterContext context, Operand a, bool neg) + { + if (neg) + { + a = context.INegate(a); + } + + return a; + } + + public static Operand INegate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Negate, Local(), a); + } + + public static Operand ISubtract(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Subtract, Local(), a, b); + } + + public static Operand IsNan(this EmitterContext context, Operand a) + { + return context.Add(Instruction.IsNan, Local(), a); + } + + public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadConstant, Local(), a, b); + } + + public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.PackHalf2x16, Local(), a, b); + } + + public static Operand Return(this EmitterContext context) + { + context.PrepareForReturn(); + + return context.Add(Instruction.Return); + } + + public static Operand ShiftLeft(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftLeft, Local(), a, b); + } + + public static Operand ShiftRightS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightS32, Local(), a, b); + } + + public static Operand ShiftRightU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightU32, Local(), a, b); + } + + public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 1); + } + + public static Operand UnpackHalf2x16Low(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 0); + } + + private static Operand UnpackHalf2x16(this EmitterContext context, Operand a, int index) + { + Operand dest = Local(); + + context.Add(new Operation(Instruction.UnpackHalf2x16, index, dest, a)); + + return dest; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs new file mode 100644 index 000000000..2b0f19052 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs @@ -0,0 +1,64 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BranchElimination + { + public static bool Eliminate(BasicBlock block) + { + if (block.HasBranch && IsRedundantBranch((Operation)block.GetLastOp(), Next(block))) + { + block.Branch = null; + + return true; + } + + return false; + } + + private static bool IsRedundantBranch(Operation current, BasicBlock nextBlock) + { + //Here we check that: + //- The current block ends with a branch. + //- The next block only contains a branch. + //- The branch on the next block is unconditional. + //- Both branches are jumping to the same location. + //In this case, the branch on the current block can be removed, + //as the next block is going to jump to the same place anyway. + if (nextBlock == null) + { + return false; + } + + if (!(nextBlock.Operations.First?.Value is Operation next)) + { + return false; + } + + if (next.Inst != Instruction.Branch) + { + return false; + } + + return current.Dest == next.Dest; + } + + private static BasicBlock Next(BasicBlock block) + { + block = block.Next; + + while (block != null && block.Operations.Count == 0) + { + if (block.HasBranch) + { + throw new InvalidOperationException("Found a bogus empty block that \"ends with a branch\"."); + } + + block = block.Next; + } + + return block; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs new file mode 100644 index 000000000..a2e05ef12 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs @@ -0,0 +1,323 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class ConstantFolding + { + public static void Fold(Operation operation) + { + if (!AreAllSourcesConstant(operation)) + { + return; + } + + switch (operation.Inst) + { + case Instruction.Add: + EvaluateBinary(operation, (x, y) => x + y); + break; + + case Instruction.BitwiseAnd: + EvaluateBinary(operation, (x, y) => x & y); + break; + + case Instruction.BitwiseExclusiveOr: + EvaluateBinary(operation, (x, y) => x ^ y); + break; + + case Instruction.BitwiseNot: + EvaluateUnary(operation, (x) => ~x); + break; + + case Instruction.BitwiseOr: + EvaluateBinary(operation, (x, y) => x | y); + break; + + case Instruction.BitfieldExtractS32: + BitfieldExtractS32(operation); + break; + + case Instruction.BitfieldExtractU32: + BitfieldExtractU32(operation); + break; + + case Instruction.Clamp: + EvaluateTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.ClampU32: + EvaluateTernary(operation, (x, y, z) => (int)Math.Clamp((uint)x, (uint)y, (uint)z)); + break; + + case Instruction.CompareEqual: + EvaluateBinary(operation, (x, y) => x == y); + break; + + case Instruction.CompareGreater: + EvaluateBinary(operation, (x, y) => x > y); + break; + + case Instruction.CompareGreaterOrEqual: + EvaluateBinary(operation, (x, y) => x >= y); + break; + + case Instruction.CompareGreaterOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x >= (uint)y); + break; + + case Instruction.CompareGreaterU32: + EvaluateBinary(operation, (x, y) => (uint)x > (uint)y); + break; + + case Instruction.CompareLess: + EvaluateBinary(operation, (x, y) => x < y); + break; + + case Instruction.CompareLessOrEqual: + EvaluateBinary(operation, (x, y) => x <= y); + break; + + case Instruction.CompareLessOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x <= (uint)y); + break; + + case Instruction.CompareLessU32: + EvaluateBinary(operation, (x, y) => (uint)x < (uint)y); + break; + + case Instruction.CompareNotEqual: + EvaluateBinary(operation, (x, y) => x != y); + break; + + case Instruction.Divide: + EvaluateBinary(operation, (x, y) => y != 0 ? x / y : 0); + break; + + case Instruction.FP | Instruction.Add: + EvaluateFPBinary(operation, (x, y) => x + y); + break; + + case Instruction.FP | Instruction.Clamp: + EvaluateFPTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.FP | Instruction.CompareEqual: + EvaluateFPBinary(operation, (x, y) => x == y); + break; + + case Instruction.FP | Instruction.CompareGreater: + EvaluateFPBinary(operation, (x, y) => x > y); + break; + + case Instruction.FP | Instruction.CompareGreaterOrEqual: + EvaluateFPBinary(operation, (x, y) => x >= y); + break; + + case Instruction.FP | Instruction.CompareLess: + EvaluateFPBinary(operation, (x, y) => x < y); + break; + + case Instruction.FP | Instruction.CompareLessOrEqual: + EvaluateFPBinary(operation, (x, y) => x <= y); + break; + + case Instruction.FP | Instruction.CompareNotEqual: + EvaluateFPBinary(operation, (x, y) => x != y); + break; + + case Instruction.FP | Instruction.Divide: + EvaluateFPBinary(operation, (x, y) => x / y); + break; + + case Instruction.FP | Instruction.Multiply: + EvaluateFPBinary(operation, (x, y) => x * y); + break; + + case Instruction.FP | Instruction.Negate: + EvaluateFPUnary(operation, (x) => -x); + break; + + case Instruction.FP | Instruction.Subtract: + EvaluateFPBinary(operation, (x, y) => x - y); + break; + + case Instruction.IsNan: + EvaluateFPUnary(operation, (x) => float.IsNaN(x)); + break; + + case Instruction.Maximum: + EvaluateBinary(operation, (x, y) => Math.Max(x, y)); + break; + + case Instruction.MaximumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Max((uint)x, (uint)y)); + break; + + case Instruction.Minimum: + EvaluateBinary(operation, (x, y) => Math.Min(x, y)); + break; + + case Instruction.MinimumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Min((uint)x, (uint)y)); + break; + + case Instruction.Multiply: + EvaluateBinary(operation, (x, y) => x * y); + break; + + case Instruction.Negate: + EvaluateUnary(operation, (x) => -x); + break; + + case Instruction.ShiftLeft: + EvaluateBinary(operation, (x, y) => x << y); + break; + + case Instruction.ShiftRightS32: + EvaluateBinary(operation, (x, y) => x >> y); + break; + + case Instruction.ShiftRightU32: + EvaluateBinary(operation, (x, y) => (int)((uint)x >> y)); + break; + + case Instruction.Subtract: + EvaluateBinary(operation, (x, y) => x - y); + break; + + case Instruction.UnpackHalf2x16: + UnpackHalf2x16(operation); + break; + } + } + + private static bool AreAllSourcesConstant(Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index).Type != OperandType.Constant) + { + return false; + } + } + + return true; + } + + private static void BitfieldExtractS32(Operation operation) + { + int value = GetBitfieldExtractValue(operation); + + int shift = 32 - operation.GetSource(2).Value; + + value = (value << shift) >> shift; + + operation.TurnIntoCopy(Const(value)); + } + + private static void BitfieldExtractU32(Operation operation) + { + operation.TurnIntoCopy(Const(GetBitfieldExtractValue(operation))); + } + + private static int GetBitfieldExtractValue(Operation operation) + { + int value = operation.GetSource(0).Value; + int lsb = operation.GetSource(1).Value; + int length = operation.GetSource(2).Value; + + return value.Extract(lsb, length); + } + + private static void UnpackHalf2x16(Operation operation) + { + int value = operation.GetSource(0).Value; + + value = (value >> operation.ComponentIndex * 16) & 0xffff; + + operation.TurnIntoCopy(ConstF(HalfConversion.HalfToSingle(value))); + } + + private static void FPNegate(Operation operation) + { + float value = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(ConstF(-value)); + } + + private static void EvaluateUnary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(Const(op(x) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateBinary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y))); + } + + private static void EvaluateBinary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateFPBinary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y))); + } + + private static void EvaluateFPBinary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateTernary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + int z = operation.GetSource(2).Value; + + operation.TurnIntoCopy(Const(op(x, y, z))); + } + + private static void EvaluateFPTernary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + float z = operation.GetSource(2).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y, z))); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs new file mode 100644 index 000000000..9ef35abc9 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs @@ -0,0 +1,47 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class HalfConversion + { + public static float HalfToSingle(int value) + { + int mantissa = (value >> 0) & 0x3ff; + int exponent = (value >> 10) & 0x1f; + int sign = (value >> 15) & 0x1; + + if (exponent == 0x1f) + { + //NaN or Infinity. + mantissa <<= 13; + exponent = 0xff; + } + else if (exponent != 0 || mantissa != 0 ) + { + if (exponent == 0) + { + //Denormal. + int e = -1; + int m = mantissa; + + do + { + e++; + m <<= 1; + } + while ((m & 0x400) == 0); + + mantissa = m & 0x3ff; + exponent = e; + } + + mantissa <<= 13; + exponent = 127 - 15 + exponent; + } + + int output = (sign << 31) | (exponent << 23) | mantissa; + + return BitConverter.Int32BitsToSingle(output); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs new file mode 100644 index 000000000..88118e3a7 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs @@ -0,0 +1,172 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Optimizer + { + public static void Optimize(BasicBlock[] blocks) + { + bool modified; + + do + { + modified = false; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + bool isUnused = IsUnused(node.Value); + + if (!(node.Value is Operation operation) || isUnused) + { + if (isUnused) + { + RemoveNode(block, node); + + modified = true; + } + + node = nextNode; + + continue; + } + + ConstantFolding.Fold(operation); + + Simplification.Simplify(operation); + + if (DestIsLocalVar(operation)) + { + if (operation.Inst == Instruction.Copy) + { + PropagateCopy(operation); + + RemoveNode(block, node); + + modified = true; + } + else if (operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation)) + { + if (operation.Dest.UseOps.Count == 0) + { + RemoveNode(block, node); + } + + modified = true; + } + } + + node = nextNode; + } + + if (BranchElimination.Eliminate(block)) + { + RemoveNode(block, block.Operations.Last); + + modified = true; + } + } + } + while (modified); + } + + private static void PropagateCopy(Operation copyOp) + { + //Propagate copy source operand to all uses of + //the destination operand. + Operand dest = copyOp.Dest; + Operand src = copyOp.GetSource(0); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + for (int index = 0; index < useNode.SourcesCount; index++) + { + if (useNode.GetSource(index) == dest) + { + useNode.SetSource(index, src); + } + } + } + } + + private static bool PropagatePack(Operation packOp) + { + //Propagate pack source operands to uses by unpack + //instruction. The source depends on the unpack instruction. + bool modified = false; + + Operand dest = packOp.Dest; + Operand src0 = packOp.GetSource(0); + Operand src1 = packOp.GetSource(1); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + if (!(useNode is Operation operation) || operation.Inst != Instruction.UnpackHalf2x16) + { + continue; + } + + if (operation.GetSource(0) == dest) + { + operation.TurnIntoCopy(operation.ComponentIndex == 1 ? src1 : src0); + + modified = true; + } + } + + return modified; + } + + private static void RemoveNode(BasicBlock block, LinkedListNode llNode) + { + //Remove a node from the nodes list, and also remove itself + //from all the use lists on the operands that this node uses. + block.Operations.Remove(llNode); + + Queue nodes = new Queue(); + + nodes.Enqueue(llNode.Value); + + while (nodes.TryDequeue(out INode node)) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (src.Type != OperandType.LocalVariable) + { + continue; + } + + if (src.UseOps.Remove(node) && src.UseOps.Count == 0) + { + nodes.Enqueue(src.AsgOp); + } + } + } + } + + private static bool IsUnused(INode node) + { + return DestIsLocalVar(node) && node.Dest.UseOps.Count == 0; + } + + private static bool DestIsLocalVar(INode node) + { + return node.Dest != null && node.Dest.Type == OperandType.LocalVariable; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs new file mode 100644 index 000000000..56b1543f1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs @@ -0,0 +1,147 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Simplification + { + private const int AllOnes = ~0; + + public static void Simplify(Operation operation) + { + switch (operation.Inst) + { + case Instruction.Add: + case Instruction.BitwiseExclusiveOr: + TryEliminateBinaryOpComutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(operation); + break; + + case Instruction.ConditionalSelect: + TryEliminateConditionalSelect(operation); + break; + + case Instruction.Divide: + TryEliminateBinaryOpY(operation, 1); + break; + + case Instruction.Multiply: + TryEliminateBinaryOpComutative(operation, 1); + break; + + case Instruction.ShiftLeft: + case Instruction.ShiftRightS32: + case Instruction.ShiftRightU32: + case Instruction.Subtract: + TryEliminateBinaryOpY(operation, 0); + break; + } + } + + private static void TryEliminateBitwiseAnd(Operation operation) + { + //Try to recognize and optimize those 3 patterns (in order): + //x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y, + //x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000 + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, AllOnes)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, 0) || IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(Const(0)); + } + } + + private static void TryEliminateBitwiseOr(Operation operation) + { + //Try to recognize and optimize those 3 patterns (in order): + //x | 0x00000000 == x, 0x00000000 | y == y, + //x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, 0)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, AllOnes) || IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(Const(AllOnes)); + } + } + + private static void TryEliminateBinaryOpY(Operation operation, int comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateBinaryOpComutative(Operation operation, int comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, comparand)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateConditionalSelect(Operation operation) + { + Operand cond = operation.GetSource(0); + + if (cond.Type != OperandType.Constant) + { + return; + } + + //The condition is constant, we can turn it into a copy, and select + //the source based on the condition value. + int srcIndex = cond.Value != 0 ? 1 : 2; + + Operand source = operation.GetSource(srcIndex); + + operation.TurnIntoCopy(source); + } + + private static bool IsConstEqual(Operand operand, int comparand) + { + if (operand.Type != OperandType.Constant) + { + return false; + } + + return operand.Value == comparand; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Ssa.cs b/Ryujinx.Graphics/Shader/Translation/Ssa.cs new file mode 100644 index 000000000..b612649ca --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Ssa.cs @@ -0,0 +1,330 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Ssa + { + private const int GprsAndPredsCount = RegisterConsts.GprsCount + RegisterConsts.PredsCount; + + private class DefMap + { + private Dictionary _map; + + private long[] _phiMasks; + + public DefMap() + { + _map = new Dictionary(); + + _phiMasks = new long[(RegisterConsts.TotalCount + 63) / 64]; + } + + public bool TryAddOperand(Register reg, Operand operand) + { + return _map.TryAdd(reg, operand); + } + + public bool TryGetOperand(Register reg, out Operand operand) + { + return _map.TryGetValue(reg, out operand); + } + + public bool AddPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + long mask = 1L << bit; + + if ((_phiMasks[index] & mask) != 0) + { + return false; + } + + _phiMasks[index] |= mask; + + return true; + } + + public bool HasPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + return (_phiMasks[index] & (1L << bit)) != 0; + } + } + + private struct Definition + { + public BasicBlock Block { get; } + public Operand Local { get; } + + public Definition(BasicBlock block, Operand local) + { + Block = block; + Local = local; + } + } + + public static void Rename(BasicBlock[] blocks) + { + DefMap[] globalDefs = new DefMap[blocks.Length]; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + globalDefs[blkIndex] = new DefMap(); + } + + Queue dfPhiBlocks = new Queue(); + + //First pass, get all defs and locals uses. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + Operand RenameLocal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + Operand local = localDefs[GetKeyFromRegister(operand.GetRegister())]; + + operand = local ?? operand; + } + + return operand; + } + + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameLocal(operation.GetSource(index))); + } + + if (operation.Dest != null && operation.Dest.Type == OperandType.Register) + { + Operand local = Local(); + + localDefs[GetKeyFromRegister(operation.Dest.GetRegister())] = local; + + operation.Dest = local; + } + } + + node = node.Next; + } + + for (int index = 0; index < RegisterConsts.TotalCount; index++) + { + Operand local = localDefs[index]; + + if (local == null) + { + continue; + } + + Register reg = GetRegisterFromKey(index); + + globalDefs[block.Index].TryAddOperand(reg, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(reg)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + } + + //Second pass, rename variables with definitions on different blocks. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + BasicBlock block = blocks[blkIndex]; + + Operand RenameGlobal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + int key = GetKeyFromRegister(operand.GetRegister()); + + Operand local = localDefs[key]; + + if (local != null) + { + return local; + } + + operand = FindDefinitionForCurr(globalDefs, block, operand.GetRegister()); + + localDefs[key] = operand; + } + + return operand; + } + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameGlobal(operation.GetSource(index))); + } + } + + node = node.Next; + } + } + } + + private static Operand FindDefinitionForCurr(DefMap[] globalDefs, BasicBlock current, Register reg) + { + if (globalDefs[current.Index].HasPhi(reg)) + { + return InsertPhi(globalDefs, current, reg); + } + + if (current != current.ImmediateDominator) + { + return FindDefinition(globalDefs, current.ImmediateDominator, reg).Local; + } + + return Undef(); + } + + private static Definition FindDefinition(DefMap[] globalDefs, BasicBlock current, Register reg) + { + foreach (BasicBlock block in SelfAndImmediateDominators(current)) + { + DefMap defMap = globalDefs[block.Index]; + + if (defMap.TryGetOperand(reg, out Operand lastDef)) + { + return new Definition(block, lastDef); + } + + if (defMap.HasPhi(reg)) + { + return new Definition(block, InsertPhi(globalDefs, block, reg)); + } + } + + return new Definition(current, Undef()); + } + + private static IEnumerable SelfAndImmediateDominators(BasicBlock block) + { + while (block != block.ImmediateDominator) + { + yield return block; + + block = block.ImmediateDominator; + } + + yield return block; + } + + private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Register reg) + { + //This block has a Phi that has not been materialized yet, but that + //would define a new version of the variable we're looking for. We need + //to materialize the Phi, add all the block/operand pairs into the Phi, and + //then use the definition from that Phi. + Operand local = Local(); + + PhiNode phi = new PhiNode(local); + + AddPhi(block, phi); + + globalDefs[block.Index].TryAddOperand(reg, local); + + foreach (BasicBlock predecessor in block.Predecessors) + { + Definition def = FindDefinition(globalDefs, predecessor, reg); + + phi.AddSource(def.Block, def.Local); + } + + return local; + } + + private static void AddPhi(BasicBlock block, PhiNode phi) + { + LinkedListNode node = block.Operations.First; + + if (node != null) + { + while (node.Next?.Value is PhiNode) + { + node = node.Next; + } + } + + if (node?.Value is PhiNode) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static int GetKeyFromRegister(Register reg) + { + if (reg.Type == RegisterType.Gpr) + { + return reg.Index; + } + else if (reg.Type == RegisterType.Predicate) + { + return RegisterConsts.GprsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.Flag) */ + { + return GprsAndPredsCount + reg.Index; + } + } + + private static Register GetRegisterFromKey(int key) + { + if (key < RegisterConsts.GprsCount) + { + return new Register(key, RegisterType.Gpr); + } + else if (key < GprsAndPredsCount) + { + return new Register(key - RegisterConsts.GprsCount, RegisterType.Predicate); + } + else /* if (key < RegisterConsts.TotalCount) */ + { + return new Register(key - GprsAndPredsCount, RegisterType.Flag); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Translator.cs b/Ryujinx.Graphics/Shader/Translation/Translator.cs new file mode 100644 index 000000000..706f3cfa4 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Translator.cs @@ -0,0 +1,219 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.CodeGen.Glsl; +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public static class Translator + { + public static ShaderProgram Translate(IGalMemory memory, ulong address, ShaderConfig config) + { + return Translate(memory, address, 0, config); + } + + public static ShaderProgram Translate( + IGalMemory memory, + ulong address, + ulong addressB, + ShaderConfig config) + { + Operation[] shaderOps = DecodeShader(memory, address, config.Type); + + if (addressB != 0) + { + //Dual vertex shader. + Operation[] shaderOpsB = DecodeShader(memory, addressB, config.Type); + + shaderOps = Combine(shaderOps, shaderOpsB); + } + + BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(shaderOps); + + Dominance.FindDominators(irBlocks[0], irBlocks.Length); + + Dominance.FindDominanceFrontiers(irBlocks); + + Ssa.Rename(irBlocks); + + Optimizer.Optimize(irBlocks); + + StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(irBlocks); + + GlslProgram program = GlslGenerator.Generate(sInfo, config); + + ShaderProgramInfo spInfo = new ShaderProgramInfo( + program.CBufferDescriptors, + program.TextureDescriptors); + + return new ShaderProgram(spInfo, program.Code); + } + + private static Operation[] DecodeShader(IGalMemory memory, ulong address, GalShaderType shaderType) + { + ShaderHeader header = new ShaderHeader(memory, address); + + Block[] cfg = Decoder.Decode(memory, address); + + EmitterContext context = new EmitterContext(shaderType, header); + + for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++) + { + Block block = cfg[blkIndex]; + + context.CurrBlock = block; + + context.MarkLabel(context.GetLabel(block.Address)); + + for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++) + { + OpCode op = block.OpCodes[opIndex]; + + if (op.NeverExecute) + { + continue; + } + + Operand predSkipLbl = null; + + bool skipPredicateCheck = op.Emitter == InstEmit.Bra; + + if (op is OpCodeSync opSync) + { + //If the instruction is a SYNC instruction with only one + //possible target address, then the instruction is basically + //just a simple branch, we can generate code similar to branch + //instructions, with the condition check on the branch itself. + skipPredicateCheck |= opSync.Targets.Count < 2; + } + + if (!(op.Predicate.IsPT || skipPredicateCheck)) + { + Operand label; + + if (opIndex == block.OpCodes.Count - 1 && block.Next != null) + { + label = context.GetLabel(block.Next.Address); + } + else + { + label = Label(); + + predSkipLbl = label; + } + + Operand pred = Register(op.Predicate); + + if (op.InvertPredicate) + { + context.BranchIfTrue(label, pred); + } + else + { + context.BranchIfFalse(label, pred); + } + } + + context.CurrOp = op; + + op.Emitter(context); + + if (predSkipLbl != null) + { + context.MarkLabel(predSkipLbl); + } + } + } + + return context.GetOperations(); + } + + private static Operation[] Combine(Operation[] a, Operation[] b) + { + //Here we combine two shaders. + //For shader A: + //- All user attribute stores on shader A are turned into copies to a + //temporary variable. It's assumed that shader B will consume them. + //- All return instructions are turned into branch instructions, the + //branch target being the start of the shader B code. + //For shader B: + //- All user attribute loads on shader B are turned into copies from a + //temporary variable, as long that attribute is written by shader A. + List output = new List(a.Length + b.Length); + + Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4]; + + Operand lblB = Label(); + + for (int index = 0; index < a.Length; index++) + { + Operation operation = a[index]; + + if (IsUserAttribute(operation.Dest)) + { + int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4; + + Operand temp = temps[tIndex]; + + if (temp == null) + { + temp = Local(); + + temps[tIndex] = temp; + } + + operation.Dest = temp; + } + + if (operation.Inst == Instruction.Return) + { + output.Add(new Operation(Instruction.Branch, lblB)); + } + else + { + output.Add(operation); + } + } + + output.Add(new Operation(Instruction.MarkLabel, lblB)); + + for (int index = 0; index < b.Length; index++) + { + Operation operation = b[index]; + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + Operand src = operation.GetSource(srcIndex); + + if (IsUserAttribute(src)) + { + Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4]; + + if (temp != null) + { + operation.SetSource(srcIndex, temp); + } + } + } + + output.Add(operation); + } + + return output.ToArray(); + } + + private static bool IsUserAttribute(Operand operand) + { + return operand != null && + operand.Type == OperandType.Attribute && + operand.Value >= AttributeConsts.UserAttributeBase && + operand.Value < AttributeConsts.UserAttributeEnd; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Texture/TextureInstructionSuffix.cs b/Ryujinx.Graphics/Texture/TextureInstructionSuffix.cs deleted file mode 100644 index 65a8f356b..000000000 --- a/Ryujinx.Graphics/Texture/TextureInstructionSuffix.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Texture -{ - [Flags] - public enum TextureInstructionSuffix - { - None = 0x00, // No Modifier - Lz = 0x02, // Load LOD Zero - Lb = 0x08, // Load Bias - Ll = 0x10, // Load LOD - Lba = 0x20, // Load Bias with OperA? Auto? - Lla = 0x40, // Load LOD with OperA? Auto? - Dc = 0x80, // Depth Compare - AOffI = 0x100, // Offset - Mz = 0x200, // Multisample Zero? - Ptp = 0x400 // ??? - } -} diff --git a/Ryujinx.ShaderTools/Memory.cs b/Ryujinx.ShaderTools/Memory.cs index f801ab39a..c99224b5e 100644 --- a/Ryujinx.ShaderTools/Memory.cs +++ b/Ryujinx.ShaderTools/Memory.cs @@ -23,4 +23,4 @@ namespace Ryujinx.ShaderTools return Reader.ReadInt32(); } } -} +} \ No newline at end of file diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs index 77aba0abe..e763e2c1c 100644 --- a/Ryujinx.ShaderTools/Program.cs +++ b/Ryujinx.ShaderTools/Program.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Gal.Shader; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; using System; using System.IO; @@ -7,32 +8,30 @@ namespace Ryujinx.ShaderTools { class Program { - private static readonly int MaxUboSize = 65536; - static void Main(string[] args) { if (args.Length == 2) { - GlslDecompiler Decompiler = new GlslDecompiler(MaxUboSize, true); - - GalShaderType ShaderType = GalShaderType.Vertex; + GalShaderType type = GalShaderType.Vertex; switch (args[0].ToLower()) { - case "v": ShaderType = GalShaderType.Vertex; break; - case "tc": ShaderType = GalShaderType.TessControl; break; - case "te": ShaderType = GalShaderType.TessEvaluation; break; - case "g": ShaderType = GalShaderType.Geometry; break; - case "f": ShaderType = GalShaderType.Fragment; break; + case "v": type = GalShaderType.Vertex; break; + case "tc": type = GalShaderType.TessControl; break; + case "te": type = GalShaderType.TessEvaluation; break; + case "g": type = GalShaderType.Geometry; break; + case "f": type = GalShaderType.Fragment; break; } - using (FileStream FS = new FileStream(args[1], FileMode.Open, FileAccess.Read)) + using (FileStream fs = new FileStream(args[1], FileMode.Open, FileAccess.Read)) { - Memory Mem = new Memory(FS); + Memory mem = new Memory(fs); - GlslProgram Program = Decompiler.Decompile(Mem, 0, ShaderType); + ShaderConfig config = new ShaderConfig(type, 65536); - Console.WriteLine(Program.Code); + string code = Translator.Translate(mem, 0, config).Code; + + Console.WriteLine(code); } } else @@ -41,4 +40,4 @@ namespace Ryujinx.ShaderTools } } } -} +} \ No newline at end of file diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 1b26b3918..42a6a7415 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -75,7 +75,7 @@ namespace Ryujinx break; } } - else + else { Logger.PrintWarning(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file"); }