From 6f10a638205eab60a6b20c9216f74a47642f1b6a Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 28 Mar 2019 01:37:34 -0300 Subject: [PATCH] Start implementing a new shader translator --- Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs | 7 +- Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs | 2 +- .../Shader/CodeGen/Glsl/CodeGenContext.cs | 103 +++ .../Shader/CodeGen/Glsl/Declarations.cs | 136 ++++ .../Shader/CodeGen/Glsl/DefaultNames.cs | 15 + .../Shader/CodeGen/Glsl/GlslGenerator.cs | 125 ++++ .../Shader/CodeGen/Glsl/Instructions.cs | 380 ++++++++++ .../Shader/CodeGen/Glsl/OperandManager.cs | 173 +++++ .../Shader/CodeGen/Glsl/TypeConversion.cs | 52 ++ .../Shader/Decoders/BitfieldExtensions.cs | 25 + Ryujinx.Graphics/Shader/Decoders/Block.cs | 38 + Ryujinx.Graphics/Shader/Decoders/Condition.cs | 45 ++ .../Shader/Decoders/ConditionalOperation.cs | 10 + Ryujinx.Graphics/Shader/Decoders/Decoder.cs | 349 +++++++++ .../Shader/Decoders/DecoderHelper.cs | 37 + Ryujinx.Graphics/Shader/Decoders/FPType.cs | 9 + Ryujinx.Graphics/Shader/Decoders/FmulScale.cs | 13 + Ryujinx.Graphics/Shader/Decoders/IOpCode.cs | 16 + .../Shader/Decoders/IOpCodeAlu.cs | 12 + .../Shader/Decoders/IOpCodeCbuf.cs | 8 + .../Shader/Decoders/IOpCodeFArith.cs | 12 + .../Shader/Decoders/IOpCodeImm.cs | 7 + .../Shader/Decoders/IOpCodeImmF.cs | 7 + .../Shader/Decoders/IOpCodeLop.cs | 10 + Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs | 7 + Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs | 7 + Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs | 7 + .../Shader/Decoders/IOpCodeReg.cs | 7 + .../Shader/Decoders/IOpCodeRegCbuf.cs | 10 + .../Shader/Decoders/IntegerSize.cs | 12 + .../Shader/Decoders/IntegerType.cs | 14 + .../Shader/Decoders/LogicalOperation.cs | 10 + Ryujinx.Graphics/Shader/Decoders/OpCode.cs | 30 + Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs | 34 + .../Shader/Decoders/OpCodeAluCbuf.cs | 16 + .../Shader/Decoders/OpCodeAluImm.cs | 14 + .../Shader/Decoders/OpCodeAluImm32.cs | 18 + .../Shader/Decoders/OpCodeAluReg.cs | 14 + .../Shader/Decoders/OpCodeAluRegCbuf.cs | 18 + .../Shader/Decoders/OpCodeAttribute.cs | 16 + .../Shader/Decoders/OpCodeBranch.cs | 19 + .../Shader/Decoders/OpCodeExit.cs | 12 + .../Shader/Decoders/OpCodeFArith.cs | 24 + .../Shader/Decoders/OpCodeFArithCbuf.cs | 16 + .../Shader/Decoders/OpCodeFArithImm.cs | 14 + .../Shader/Decoders/OpCodeFArithImm32.cs | 30 + .../Shader/Decoders/OpCodeFArithReg.cs | 14 + .../Shader/Decoders/OpCodeFArithRegCbuf.cs | 18 + .../Shader/Decoders/OpCodeFsetImm.cs | 14 + Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs | 14 + Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs | 26 + Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs | 28 + .../Shader/Decoders/OpCodeLopCbuf.cs | 16 + .../Shader/Decoders/OpCodeLopImm.cs | 14 + .../Shader/Decoders/OpCodeLopImm32.cs | 22 + .../Shader/Decoders/OpCodeLopReg.cs | 14 + .../Shader/Decoders/OpCodePsetp.cs | 20 + Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs | 26 + .../Shader/Decoders/OpCodeSetCbuf.cs | 16 + .../Shader/Decoders/OpCodeSetImm.cs | 14 + .../Shader/Decoders/OpCodeSetReg.cs | 14 + Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs | 20 + .../Shader/Decoders/OpCodeSync.cs | 15 + .../Shader/Decoders/OpCodeTable.cs | 194 +++++ Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs | 46 ++ .../Shader/Decoders/OpCodeTexs.cs | 68 ++ Ryujinx.Graphics/Shader/Decoders/Register.cs | 36 + .../Shader/Decoders/RegisterConsts.cs | 13 + .../Shader/Decoders/RegisterType.cs | 9 + .../Shader/Decoders/RoundingMode.cs | 10 + Ryujinx.Graphics/Shader/Decoders/TexsType.cs | 20 + .../Shader/Decoders/TextureDimensions.cs | 10 + .../Shader/Decoders/TextureLodMode.cs | 12 + .../Shader/Instructions/Iadd3Mode.cs | 9 + .../Shader/Instructions/Iadd3Part.cs | 9 + .../Shader/Instructions/InstEmitAlu.cs | 684 ++++++++++++++++++ .../Shader/Instructions/InstEmitAluHelper.cs | 88 +++ .../Shader/Instructions/InstEmitConversion.cs | 200 +++++ .../Shader/Instructions/InstEmitFArith.cs | 286 ++++++++ .../Shader/Instructions/InstEmitFlow.cs | 100 +++ .../Shader/Instructions/InstEmitHelper.cs | 107 +++ .../Shader/Instructions/InstEmitMemory.cs | 457 ++++++++++++ .../Shader/Instructions/InstEmitMove.cs | 32 + .../Shader/Instructions/InstEmitter.cs | 6 + .../Shader/Instructions/IntegerCondition.cs | 18 + .../Shader/Instructions/Lop3Expression.cs | 149 ++++ .../Shader/Instructions/MufuOperation.cs | 15 + .../Shader/Instructions/XmadMode.cs | 11 + .../IntermediateRepresentation/BasicBlock.cs | 60 ++ .../IntermediateRepresentation/INode.cs | 13 + .../IntermediateRepresentation/Instruction.cs | 78 ++ .../IntermediateRepresentation/IrConsts.cs | 8 + .../IntermediateRepresentation/Operand.cs | 73 ++ .../OperandHelper.cs | 62 ++ .../IntermediateRepresentation/OperandType.cs | 15 + .../IntermediateRepresentation/Operation.cs | 61 ++ .../IntermediateRepresentation/PhiNode.cs | 87 +++ .../TextureOperation.cs | 23 + .../IntermediateRepresentation/TextureType.cs | 23 + Ryujinx.Graphics/Shader/ShaderHeader.cs | 166 +++++ .../Shader/StructuredIr/AstAssignment.cs | 14 + .../Shader/StructuredIr/AstBlock.cs | 22 + .../Shader/StructuredIr/AstBlockType.cs | 11 + .../Shader/StructuredIr/AstDeclaration.cs | 12 + .../Shader/StructuredIr/AstOperand.cs | 42 ++ .../Shader/StructuredIr/AstOperation.cs | 17 + .../StructuredIr/AstTextureOperation.cs | 24 + .../Shader/StructuredIr/IAstNode.cs | 7 + .../Shader/StructuredIr/InstructionInfo.cs | 124 ++++ .../Shader/StructuredIr/OperandInfo.cs | 34 + .../Shader/StructuredIr/StructuredProgram.cs | 205 ++++++ .../StructuredIr/StructuredProgramContext.cs | 311 ++++++++ .../StructuredIr/StructuredProgramInfo.cs | 29 + .../Shader/StructuredIr/VariableType.cs | 13 + .../Shader/Translation/AttributeConsts.cs | 30 + .../Shader/Translation/ControlFlowGraph.cs | 108 +++ .../Shader/Translation/Dominance.cs | 127 ++++ .../Shader/Translation/EmitterContext.cs | 105 +++ .../Shader/Translation/EmitterContextInsts.cs | 386 ++++++++++ .../Shader/Translation/Optimizer.cs | 116 +++ Ryujinx.Graphics/Shader/Translation/Ssa.cs | 330 +++++++++ .../Shader/Translation/Translator.cs | 109 +++ Ryujinx.ShaderTools/Program.cs | 10 +- Ryujinx/Program.cs | 2 +- 124 files changed, 7684 insertions(+), 7 deletions(-) create mode 100644 Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs create mode 100644 Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs create mode 100644 Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs create mode 100644 Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs create mode 100644 Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs create mode 100644 Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs create mode 100644 Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/Block.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/Condition.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/ConditionalOperation.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/Decoder.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/FPType.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/FmulScale.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCode.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeImmF.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeLop.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeReg.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/IntegerType.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCode.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAluCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAluReg.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAluRegCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAttribute.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeFArithCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeFArithReg.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeLopCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm32.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeLopReg.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeSetCbuf.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeSetImm.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeSetReg.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/Register.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/RegisterConsts.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/RegisterType.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/TexsType.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/Iadd3Mode.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/Iadd3Part.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/IntegerCondition.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/MufuOperation.cs create mode 100644 Ryujinx.Graphics/Shader/Instructions/XmadMode.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/INode.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs create mode 100644 Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs create mode 100644 Ryujinx.Graphics/Shader/ShaderHeader.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs create mode 100644 Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/Dominance.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/EmitterContext.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/Optimizer.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/Ssa.cs create mode 100644 Ryujinx.Graphics/Shader/Translation/Translator.cs diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs index 228a901851..1b6e993706 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -1,4 +1,5 @@ using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Texture; using System; using System.Collections.Generic; @@ -138,6 +139,8 @@ namespace Ryujinx.Graphics.Gal.Shader return Decompile(); } + private string _newCode; + public GlslProgram Decompile(IGalMemory memory, long position, GalShaderType shaderType) { _header = new ShaderHeader(memory, position); @@ -148,6 +151,8 @@ namespace Ryujinx.Graphics.Gal.Shader _decl = new GlslDecl(_blocks, shaderType, _header); + _newCode = Translator.Translate(memory, (ulong)position, shaderType); + return Decompile(); } @@ -184,7 +189,7 @@ namespace Ryujinx.Graphics.Gal.Shader PrintMain(); - string glslCode = _sb.ToString(); + string glslCode = _newCode; //_sb.ToString(); List textureInfo = new List(); diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs index 605cbda816..ecc5894508 100644 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs @@ -464,7 +464,7 @@ namespace Ryujinx.Graphics.Graphics3d left = _viewportX1 - (left - _viewportX0); right = _viewportX1 - (right - _viewportX0); } - + // Ensure X is in the right order if (left > right) { diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs new file mode 100644 index 0000000000..8c095b772b --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs @@ -0,0 +1,103 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Shader.StructuredIr; +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class CodeGenContext + { + private const string Tab = " "; + + public StructuredProgramInfo Info { get; } + + public GalShaderType ShaderType { get; } + + private StringBuilder _sb; + + private Dictionary _locals; + + private int _level; + + private string _identation; + + public CodeGenContext(StructuredProgramInfo info, GalShaderType shaderType) + { + Info = info; + ShaderType = shaderType; + + _sb = new StringBuilder(); + + _locals = new Dictionary(); + } + + 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; + } + + public string DeclareLocal(AstOperand operand) + { + string name = $"temp_{_locals.Count}"; + + _locals.Add(operand, name); + + return name; + } + + public string GetLocalName(AstOperand operand) + { + return _locals[operand]; + } + } +} \ 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 0000000000..0e09ddac4e --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs @@ -0,0 +1,136 @@ +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 prgInfo) + { + context.AppendLine("#version 410 core"); + + context.AppendLine(); + + context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;"); + + context.AppendLine(); + + context.AppendLine("layout (std140) uniform Extra"); + + context.EnterScope(); + + context.AppendLine("vec2 flip;"); + context.AppendLine("int instance;"); + + context.LeaveScope(";"); + + context.AppendLine(); + + if (prgInfo.ConstantBuffers.Count != 0) + { + DeclareUniforms(context, prgInfo); + + context.AppendLine(); + } + + if (prgInfo.Samplers.Count != 0) + { + DeclareSamplers(context, prgInfo); + + context.AppendLine(); + } + + if (prgInfo.IAttributes.Count != 0) + { + DeclareInputAttributes(context, prgInfo); + + context.AppendLine(); + } + + if (prgInfo.OAttributes.Count != 0) + { + DeclareOutputAttributes(context, prgInfo); + + context.AppendLine(); + } + } + + private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo prgInfo) + { + foreach (int cbufSlot in prgInfo.ConstantBuffers.OrderBy(x => x)) + { + string ubName = OperandManager.GetShaderStagePrefix(context.ShaderType); + + ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot; + + context.AppendLine("layout (std140) uniform " + ubName); + + context.EnterScope(); + + context.AppendLine("vec4 " + OperandManager.GetUbName(context.ShaderType, cbufSlot) + "[4096];"); + + context.LeaveScope(";"); + } + } + + private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo prgInfo) + { + foreach (KeyValuePair kv in prgInfo.Samplers.OrderBy(x => x.Key)) + { + int textureHandle = kv.Key; + + string samplerTypeName = GetSamplerTypeName(kv.Value); + + string samplerName = OperandManager.GetSamplerName(context.ShaderType, textureHandle); + + context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";"); + } + } + + private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo prgInfo) + { + foreach (int attr in prgInfo.IAttributes.OrderBy(x => x)) + { + context.AppendLine($"layout (location = {attr}) in vec4 {DefaultNames.IAttributePrefix}{attr};"); + } + } + + private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo prgInfo) + { + foreach (int attr in prgInfo.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.Array) != 0) + { + typeName += "Array"; + } + + if ((type & TextureType.DepthCompare) != 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 0000000000..6e5d129fc0 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class DefaultNames + { + 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 0000000000..a42d4db139 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs @@ -0,0 +1,125 @@ +using Ryujinx.Graphics.Gal; +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 +{ + class GlslGenerator + { + public string Generate(StructuredProgramInfo prgInfo, GalShaderType shaderType) + { + CodeGenContext cgContext = new CodeGenContext(prgInfo, shaderType); + + Declarations.Declare(cgContext, prgInfo); + + PrintBlock(cgContext, prgInfo.MainBlock); + + return cgContext.GetCode(); + } + + private void PrintBlock(CodeGenContext context, AstBlock block) + { + switch (block.Type) + { + case AstBlockType.DoWhile: + context.AppendLine("do"); + break; + + case AstBlockType.Else: + context.AppendLine("else"); + break; + + case AstBlockType.If: + context.AppendLine($"if ({GetCondExpr(context, block.Condition)})"); + break; + + case AstBlockType.Main: + context.AppendLine("void main()"); + break; + } + + context.EnterScope(); + + foreach (IAstNode node in block.Nodes) + { + if (node is AstBlock subBlock) + { + PrintBlock(context, subBlock); + } + else if (node is AstOperation operation) + { + if (operation.Inst == Instruction.Return) + { + PrepareForReturn(context); + } + + context.AppendLine(Instructions.GetExpression(context, operation) + ";"); + } + else if (node is AstAssignment asg) + { + VariableType srcType = OperandManager.GetNodeDestType(asg.Source); + VariableType dstType = OperandManager.GetNodeDestType(asg.Destination); + + string dest; + + if (asg.Destination is AstOperand operand && operand.Type == OperandType.Attribute) + { + dest = OperandManager.GetOutAttributeName(context, operand); + } + else + { + dest = Instructions.GetExpression(context, asg.Destination); + } + + string src = ReinterpretCast(Instructions.GetExpression(context, asg.Source), srcType, dstType); + + context.AppendLine(dest + " = " + src + ";"); + } + else if (node is AstDeclaration decl && decl.Operand.Type != OperandType.Undefined) + { + string name = context.DeclareLocal(decl.Operand); + + context.AppendLine(GetVarTypeName(decl.Operand.VarType) + " " + name + ";"); + } + } + + context.LeaveScope(); + + if (block.Type == AstBlockType.DoWhile) + { + context.AppendLine($"while ({GetCondExpr(context, block.Condition)});"); + } + } + + private static string GetCondExpr(CodeGenContext context, IAstNode cond) + { + VariableType srcType = OperandManager.GetNodeDestType(cond); + + return ReinterpretCast(Instructions.GetExpression(context, cond), srcType, VariableType.Bool); + } + + private 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 PrepareForReturn(CodeGenContext context) + { + if (context.ShaderType == GalShaderType.Vertex) + { + context.AppendLine("gl_Position.xy *= flip;"); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs new file mode 100644 index 0000000000..a595933684 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs @@ -0,0 +1,380 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Globalization; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class Instructions + { + public static string GetExpression(CodeGenContext context, IAstNode node) + { + if (node is AstOperation operation) + { + return GetExpression(context, operation); + } + else if (node is AstOperand operand) + { + switch (operand.Type) + { + case OperandType.Attribute: + return OperandManager.GetAttributeName(context, operand); + + case OperandType.Constant: + return "0x" + operand.Value.ToString("X8", CultureInfo.InvariantCulture); + + case OperandType.ConstantBuffer: + return OperandManager.GetConstantBufferName(context.ShaderType, operand); + + case OperandType.LocalVariable: + return context.GetLocalName(operand); + + case OperandType.Undefined: + return DefaultNames.UndefinedName; + } + + return DefaultNames.UndefinedName; + } + + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + + private static string GetExpression(CodeGenContext context, AstOperation operation) + { + switch (operation.Inst & Instruction.Mask) + { + case Instruction.Absolute: + return GetUnaryCallExpr(context, operation, "abs"); + + case Instruction.Add: + return GetBinaryExpr(context, operation, "+"); + + case Instruction.BitfieldExtractS32: + case Instruction.BitfieldExtractU32: + return GetTernaryCallExpr(context, operation, "bitfieldExtract"); + + case Instruction.BitfieldInsert: + return GetQuaternaryCallExpr(context, operation, "bitfieldInsert"); + + case Instruction.BitfieldReverse: + return GetUnaryCallExpr(context, operation, "bitfieldReverse"); + + case Instruction.BitwiseAnd: + return GetBinaryExpr(context, operation, "&"); + + case Instruction.BitwiseExclusiveOr: + return GetBinaryExpr(context, operation, "^"); + + case Instruction.BitwiseNot: + return GetUnaryExpr(context, operation, "~"); + + case Instruction.BitwiseOr: + return GetBinaryExpr(context, operation, "|"); + + case Instruction.Ceiling: + return GetUnaryCallExpr(context, operation, "ceil"); + + case Instruction.CompareEqual: + return GetBinaryExpr(context, operation, "=="); + + case Instruction.CompareGreater: + case Instruction.CompareGreaterU32: + return GetBinaryExpr(context, operation, ">"); + + case Instruction.CompareGreaterOrEqual: + case Instruction.CompareGreaterOrEqualU32: + return GetBinaryExpr(context, operation, ">="); + + case Instruction.CompareLess: + case Instruction.CompareLessU32: + return GetBinaryExpr(context, operation, "<"); + + case Instruction.CompareLessOrEqual: + case Instruction.CompareLessOrEqualU32: + return GetBinaryExpr(context, operation, "<="); + + case Instruction.CompareNotEqual: + return GetBinaryExpr(context, operation, "!="); + + case Instruction.ConditionalSelect: + return GetConditionalSelectExpr(context, operation); + + case Instruction.Cosine: + return GetUnaryCallExpr(context, operation, "cos"); + + case Instruction.Clamp: + case Instruction.ClampU32: + return GetTernaryCallExpr(context, operation, "clamp"); + + case Instruction.ConvertFPToS32: + return GetUnaryCallExpr(context, operation, "int"); + + case Instruction.ConvertS32ToFP: + case Instruction.ConvertU32ToFP: + return GetUnaryCallExpr(context, operation, "float"); + + case Instruction.Discard: + return "discard"; + + case Instruction.Divide: + return GetBinaryExpr(context, operation, "/"); + + case Instruction.ExponentB2: + return GetUnaryCallExpr(context, operation, "exp2"); + + case Instruction.Floor: + return GetUnaryCallExpr(context, operation, "floor"); + + case Instruction.FusedMultiplyAdd: + return GetTernaryCallExpr(context, operation, "fma"); + + case Instruction.IsNan: + return GetUnaryCallExpr(context, operation, "isnan"); + + case Instruction.LoadConstant: + return GetLoadConstantExpr(context, operation); + + case Instruction.LogarithmB2: + return GetUnaryCallExpr(context, operation, "log2"); + + case Instruction.Maximum: + case Instruction.MaximumU32: + return GetBinaryCallExpr(context, operation, "max"); + + case Instruction.Minimum: + case Instruction.MinimumU32: + return GetBinaryCallExpr(context, operation, "min"); + + case Instruction.Multiply: + return GetBinaryExpr(context, operation, "*"); + + case Instruction.Negate: + return GetUnaryExpr(context, operation, "-"); + + case Instruction.ReciprocalSquareRoot: + return GetUnaryCallExpr(context, operation, "inversesqrt"); + + case Instruction.Return: + return "return"; + + case Instruction.ShiftLeft: + return GetBinaryExpr(context, operation, "<<"); + + case Instruction.ShiftRightS32: + case Instruction.ShiftRightU32: + return GetBinaryExpr(context, operation, ">>"); + + case Instruction.Sine: + return GetUnaryCallExpr(context, operation, "sin"); + + case Instruction.SquareRoot: + return GetUnaryCallExpr(context, operation, "sqrt"); + + case Instruction.Subtract: + return GetBinaryExpr(context, operation, "-"); + + case Instruction.TextureSample: + return GetTextureSampleExpr(context, operation); + + case Instruction.Truncate: + return GetUnaryCallExpr(context, operation, "trunc"); + } + + return "// " + operation.Inst; + } + + private static string GetUnaryCallExpr(CodeGenContext context, AstOperation operation, string funcName) + { + return funcName + "(" + GetSoureExpr(context, operation.Sources[0], GetSrcVarType(operation.Inst, 0)) + ")"; + } + + private static string GetBinaryCallExpr(CodeGenContext context, AstOperation operation, string funcName) + { + return funcName + "(" + + GetSoureExpr(context, operation.Sources[0], GetSrcVarType(operation.Inst, 0)) + ", " + + GetSoureExpr(context, operation.Sources[1], GetSrcVarType(operation.Inst, 1)) + ")"; + } + + private static string GetTernaryCallExpr(CodeGenContext context, AstOperation operation, string funcName) + { + return funcName + "(" + + GetSoureExpr(context, operation.Sources[0], GetSrcVarType(operation.Inst, 0)) + ", " + + GetSoureExpr(context, operation.Sources[1], GetSrcVarType(operation.Inst, 1)) + ", " + + GetSoureExpr(context, operation.Sources[2], GetSrcVarType(operation.Inst, 2)) + ")"; + } + + private static string GetQuaternaryCallExpr(CodeGenContext context, AstOperation operation, string funcName) + { + return funcName + "(" + + GetSoureExpr(context, operation.Sources[0], GetSrcVarType(operation.Inst, 0)) + ", " + + GetSoureExpr(context, operation.Sources[1], GetSrcVarType(operation.Inst, 1)) + ", " + + GetSoureExpr(context, operation.Sources[2], GetSrcVarType(operation.Inst, 2)) + ", " + + GetSoureExpr(context, operation.Sources[3], GetSrcVarType(operation.Inst, 3)) + ")"; + } + + private static string GetUnaryExpr(CodeGenContext context, AstOperation operation, string op) + { + return op + GetSoureExpr(context, operation.Sources[0], GetSrcVarType(operation.Inst, 0)); + } + + private static string GetBinaryExpr(CodeGenContext context, AstOperation operation, string op) + { + return GetSoureExpr(context, operation.Sources[0], GetSrcVarType(operation.Inst, 0)) + " " + op + " " + + GetSoureExpr(context, operation.Sources[1], GetSrcVarType(operation.Inst, 1)); + } + + private static string GetConditionalSelectExpr(CodeGenContext context, AstOperation operation) + { + return "((" + + GetSoureExpr(context, operation.Sources[0], GetSrcVarType(operation.Inst, 0)) + ") ? (" + + GetSoureExpr(context, operation.Sources[1], GetSrcVarType(operation.Inst, 1)) + ") : (" + + GetSoureExpr(context, operation.Sources[2], GetSrcVarType(operation.Inst, 2)) + "))"; + } + + private static string GetLoadConstantExpr(CodeGenContext context, AstOperation operation) + { + string offsetExpr = GetSoureExpr(context, operation.Sources[1], GetSrcVarType(operation.Inst, 1)); + + return OperandManager.GetConstantBufferName(context, operation.Sources[0], offsetExpr); + } + + private static string GetTextureSampleExpr(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isShadow = (context.Info.Samplers[texOp.TextureHandle] & TextureType.DepthCompare) != 0; + + string samplerName = OperandManager.GetSamplerName(context.ShaderType, texOp.TextureHandle); + + string texCall = "texture"; + + if ((texOp.Type & TextureType.LodLevel) != 0) + { + texCall += "Lod"; + } + + if ((texOp.Type & TextureType.Offset) != 0) + { + texCall += "Offset"; + } + + texCall += "(" + samplerName; + + TextureType baseType = texOp.Type & TextureType.Mask; + + int elemsCount; + + switch (baseType) + { + case TextureType.Texture1D: elemsCount = 1; break; + case TextureType.Texture2D: elemsCount = 2; break; + case TextureType.Texture3D: elemsCount = 3; break; + case TextureType.TextureCube: elemsCount = 3; break; + + default: throw new InvalidOperationException($"Invalid texture type \"{baseType}\"."); + } + + int pCount = elemsCount; + + int arrayIndexElem = -1; + + if ((texOp.Type & TextureType.Array) != 0) + { + arrayIndexElem = pCount++; + } + + if ((texOp.Type & TextureType.DepthCompare) != 0) + { + pCount++; + } + + bool hasExtraCompareArg = false; + + if (pCount == 5) + { + pCount = 4; + + hasExtraCompareArg = true; + } + + int srcIndex = 0; + + string Src(VariableType type) + { + return GetSoureExpr(context, texOp.Sources[srcIndex++], type); + } + + string AssembleVector(int count, VariableType type, bool isP = false) + { + if (count > 1) + { + string[] vecElems = new string[count]; + + for (int index = 0; index < count; index++) + { + if (isP && index == arrayIndexElem) + { + vecElems[index] = "float(" + Src(VariableType.S32) + ")"; + } + else + { + vecElems[index] = Src(type); + } + } + + string prefix = type == VariableType.F32 ? string.Empty : "i"; + + return prefix + "vec" + count + "(" + string.Join(", ", vecElems) + ")"; + } + else + { + return Src(type); + } + } + + texCall += ", " + AssembleVector(pCount, VariableType.F32, isP: true); + + if (hasExtraCompareArg) + { + texCall += ", " + Src(VariableType.F32); + } + + if ((texOp.Type & TextureType.LodLevel) != 0) + { + texCall += ", " + Src(VariableType.F32); + } + + if ((texOp.Type & TextureType.Offset) != 0) + { + texCall += ", " + AssembleVector(elemsCount, VariableType.S32); + } + + if ((texOp.Type & TextureType.LodBias) != 0) + { + texCall += ", " + Src(VariableType.F32); + } + + texCall += ")"; + + if (!isShadow) + { + texCall += "."; + + for (int compIndex = 0; compIndex < texOp.Components.Length; compIndex++) + { + texCall += "rgba".Substring(texOp.Components[compIndex], 1); + } + } + + return texCall; + } + + private static string GetSoureExpr(CodeGenContext context, IAstNode node, VariableType dstType) + { + return ReinterpretCast(GetExpression(context, node), OperandManager.GetNodeDestType(node), dstType); + } + } +} \ 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 0000000000..908d6ea76d --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs @@ -0,0 +1,173 @@ +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) } + }; + + public static string GetConstantBufferName(GalShaderType shaderType, AstOperand cbuf) + { + string ubName = GetUbName(shaderType, cbuf.CbufSlot); + + ubName += "[" + (cbuf.CbufOffset >> 2) + "]"; + + return ubName + "." + GetSwizzleMask(cbuf.CbufOffset & 3); + } + + public static string GetConstantBufferName(CodeGenContext context, IAstNode slot, string offsetExpr) + { + //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(context.ShaderType, operand.Value); + + string index0 = "[" + offsetExpr + " >> 4]"; + string index1 = "[" + offsetExpr + " >> 2 & 3]"; + + return ubName + index0 + index1; + } + + public static string GetOutAttributeName(CodeGenContext context, AstOperand attr) + { + return GetAttributeName(context, attr, isOutAttr: true); + } + + public static string GetAttributeName(CodeGenContext context, AstOperand attr, 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; + + return $"{prefix}{(value >> 4)}.{swzMask}"; + } + 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 (context.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"; + } + } + + return builtInAttr.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, int textureHandle) + { + string suffix = (textureHandle - 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 0000000000..8ac91f1c08 --- /dev/null +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs @@ -0,0 +1,52 @@ +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(string expr, 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.S32: return $"intBitsToFloat({expr})"; + case VariableType.U32: return $"uintBitsToFloat({expr})"; + } + } + else if (srcType == VariableType.Bool) + { + return $"(({expr}) ? {IrConsts.True} : {IrConsts.False})"; + } + else if (dstType == VariableType.Bool) + { + 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}\"."); + } + } +} \ 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 0000000000..3bb9bc1f4c --- /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 0000000000..c47211ccf6 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/Block.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class Block + { + public int Index { get; set; } + + 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 OpCode GetLastOp() + { + if (OpCodes.Count > 0) + { + return OpCodes[OpCodes.Count - 1]; + } + + return null; + } + } +} \ 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 0000000000..10400f94ac --- /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 0000000000..4fc31e842b --- /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 0000000000..836927a595 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/Decoder.cs @@ -0,0 +1,349 @@ +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) + { + Dictionary visited = new Dictionary(); + Dictionary visitedEnd = new Dictionary(); + + Queue blocks = new Queue(); + + Block Enqueue(ulong addr) + { + if (!visited.TryGetValue(addr, out Block output)) + { + output = new Block(addr); + + blocks.Enqueue(output); + + visited.Add(addr, output); + } + + return output; + } + + ulong start = address + HeaderSize; + + Block entry = Enqueue(start); + + while (blocks.TryDequeue(out Block current)) + { + FillBlock(memory, current, start); + + //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.OpCodes.Count > 0) + { + foreach (OpCodeSsy ssyOp in current.SsyOpCodes) + { + Enqueue(ssyOp.GetAbsoluteAddress()); + } + + OpCode lastOp = current.GetLastOp(); + + if (lastOp is OpCodeBranch op) + { + current.Branch = Enqueue(op.GetAbsoluteAddress()); + } + + if (!IsUnconditionalBranch(lastOp)) + { + current.Next = Enqueue(current.EndAddress); + } + } + + //If we have on the graph two blocks with the same end address, + //then we need to split the bigger block and have two small blocks, + //the end address of the bigger "Current" block should then be == to + //the address of the "Smaller" block. + while (visitedEnd.TryGetValue(current.EndAddress, out Block smaller)) + { + if (current.Address > smaller.Address) + { + Block temp = smaller; + + smaller = current; + current = temp; + } + + current.EndAddress = smaller.Address; + current.Next = smaller; + current.Branch = null; + + current.OpCodes.RemoveRange( + current.OpCodes.Count - smaller.OpCodes.Count, + smaller.OpCodes.Count); + + visitedEnd[smaller.EndAddress] = smaller; + } + + visitedEnd.Add(current.EndAddress, current); + } + + foreach (Block ssyBlock in visited.Values.Where(x => x.SsyOpCodes.Count != 0)) + { + for (int ssyIndex = 0; ssyIndex < ssyBlock.SsyOpCodes.Count; ssyIndex++) + { + PropagateSsy(visited, ssyBlock, ssyIndex); + } + } + + Block[] cfg = new Block[visited.Count]; + + int index = 0; + + foreach (Block block in visited.Values.OrderBy(x => x.Address - entry.Address)) + { + block.Index = index; + + cfg[index++] = block; + } + + return cfg; + } + + 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 pending = new Stack(); + + HashSet visited = new HashSet(); + + Stack ssyStack = new Stack(); + + void Push(PathBlockState pbs) + { + if (pbs.Block == null || visited.Add(pbs.Block)) + { + pending.Push(pbs); + } + } + + Push(new PathBlockState(ssyBlock)); + + while (pending.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])); + } + } + } + } + + private static void FillBlock(IGalMemory memory, Block block, ulong start) + { + ulong address = block.Address; + + do + { + //Ignore scheduling instructions, which are written every 32 bytes. + if (((address - start) & 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); + + if (op.Emitter == InstEmit.Ssy) + { + block.SsyOpCodes.Add((OpCodeSsy)op); + } + } + while (!IsBranch(block.GetLastOp())); + + block.EndAddress = address; + } + + private static bool IsUnconditionalBranch(OpCode opCode) + { + return IsUnconditional(opCode) && IsBranch(opCode); + } + + private static bool IsUnconditional(OpCode opCode) + { + 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)); + } + } +} \ 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 0000000000..d093c09206 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs @@ -0,0 +1,37 @@ +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 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/FPType.cs b/Ryujinx.Graphics/Shader/Decoders/FPType.cs new file mode 100644 index 0000000000..e602ad45fa --- /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 0000000000..c35c6e489e --- /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 0000000000..dd6ad79a2e --- /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 0000000000..d840d49d1b --- /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 0000000000..42a174514c --- /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 0000000000..d68ccf593b --- /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/IOpCodeImm.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs new file mode 100644 index 0000000000..9cfcd69b05 --- /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 0000000000..629eff7973 --- /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 0000000000..62c87bf435 --- /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 0000000000..e5902110e5 --- /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 0000000000..bb806b95c9 --- /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 0000000000..099c4061ab --- /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 0000000000..3ed157e821 --- /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 0000000000..59c42a75c5 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRegCbuf : IOpCode + { + int Offset { get; } + int Slot { get; } + + Register Rb { get; } + } +} \ 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 0000000000..70fdfc3dc9 --- /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 0000000000..46734dbe4a --- /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 0000000000..5221442510 --- /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/OpCode.cs b/Ryujinx.Graphics/Shader/Decoders/OpCode.cs new file mode 100644 index 0000000000..b0f2ffdc32 --- /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 0000000000..15fbb9af79 --- /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 0000000000..9c12798947 --- /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 0000000000..a407fc6bf8 --- /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/OpCodeAluImm32.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs new file mode 100644 index 0000000000..5941e0b9a2 --- /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 0000000000..13b96a3ae8 --- /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 0000000000..6cf6bd2e22 --- /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 0000000000..fd8e63fcfb --- /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 0000000000..25941b3967 --- /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 0000000000..886dbff44f --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs @@ -0,0 +1,12 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeExit : OpCode + { + public OpCodeExit(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + + } + } +} \ 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 0000000000..c88f7f0ee3 --- /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 0000000000..5486bb0b07 --- /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 0000000000..1bb6f42552 --- /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 0000000000..ec9da6f302 --- /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 0000000000..55cf448597 --- /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 0000000000..4ede88423f --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithRegCbuf : OpCodeFArithReg, 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); + + Rb = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + } + } +} \ 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 0000000000..cb5f155e82 --- /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/OpCodeIpa.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs new file mode 100644 index 0000000000..e21095a325 --- /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 0000000000..cc9f065828 --- /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 0000000000..c5f903451b --- /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 0000000000..f174733c42 --- /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 0000000000..a2f091a2c4 --- /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 0000000000..cb48f3a615 --- /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 0000000000..5f43db72b5 --- /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 0000000000..729e3207e8 --- /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 0000000000..cd6773a13c --- /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 0000000000..4f3dbd7412 --- /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 0000000000..bc63b9f476 --- /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 0000000000..bbdee19622 --- /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 0000000000..499c070689 --- /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 0000000000..081d08a0de --- /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 0000000000..21df69e9f7 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs @@ -0,0 +1,194 @@ +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("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("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("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs)); + 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 0000000000..edaa70bf17 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs @@ -0,0 +1,46 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTex : 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 bool HasDepthCompare { get; } + + public bool HasOffset { get; } + + public TextureLodMode LodMode { get; } + + public OpCodeTex(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); + + HasDepthCompare = opCode.Extract(50); + + HasOffset = opCode.Extract(54); + + LodMode = (TextureLodMode)opCode.Extract(55, 3); + } + } +} \ 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 0000000000..2e1826854e --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs @@ -0,0 +1,68 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexs : 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 }, + { R___, _G__, __B_, ___A, RG__, ____, ____, ____ }, + { 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; } + + public TexsType Type { get; } + + public bool IsFp16 { get; } + + public OpCodeTexs(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); + + Type = (TexsType)opCode.Extract(53, 4); + + IsFp16 = !opCode.Extract(59); + + int rdMask; + + rdMask = Rd0.IsRZ ? 0 : 1; + rdMask |= Rd1.IsRZ ? 0 : 2; + + ComponentMask = _maskLut[rdMask, compSel]; + } + } +} \ 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 0000000000..30840d8c03 --- /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 0000000000..d381f9543e --- /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 0000000000..648f816a24 --- /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 0000000000..13bb08dc82 --- /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/TexsType.cs b/Ryujinx.Graphics/Shader/Decoders/TexsType.cs new file mode 100644 index 0000000000..6f4b726062 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/TexsType.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TexsType + { + 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/TextureDimensions.cs b/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs new file mode 100644 index 0000000000..dbdf1927fb --- /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/TextureLodMode.cs b/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs new file mode 100644 index 0000000000..0cc6f71432 --- /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/Instructions/Iadd3Mode.cs b/Ryujinx.Graphics/Shader/Instructions/Iadd3Mode.cs new file mode 100644 index 0000000000..ad203dacb9 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/Iadd3Mode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Instructions +{ + enum Iadd3Mode + { + NoShift = 0, + ShiftRight = 1, + ShiftLeft = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/Iadd3Part.cs b/Ryujinx.Graphics/Shader/Instructions/Iadd3Part.cs new file mode 100644 index 0000000000..9346751002 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/Iadd3Part.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Instructions +{ + enum Iadd3Part + { + B32 = 0, + H0 = 1, + H1 = 2 + } +} \ 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 0000000000..b535efdef3 --- /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; + + Iadd3Part partC = (Iadd3Part)op.RawOpCode.Extract(31, 2); + Iadd3Part partB = (Iadd3Part)op.RawOpCode.Extract(33, 2); + Iadd3Part partA = (Iadd3Part)op.RawOpCode.Extract(35, 2); + + Iadd3Mode mode = (Iadd3Mode)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, Iadd3Part part) + { + if (!(op is OpCodeAluReg) || part == Iadd3Part.B32) + { + return src; + } + + if (part == Iadd3Part.H0) + { + return context.BitwiseAnd(src, Const(0xffff)); + } + else if (part == Iadd3Part.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 != Iadd3Mode.NoShift) + { + if (mode == Iadd3Mode.ShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + else if (mode == Iadd3Mode.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(res, 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(res, 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; + + XmadMode mode; + + if (op is OpCodeAluReg) + { + highB = context.CurrOp.RawOpCode.Extract(35); + + mode = (XmadMode)context.CurrOp.RawOpCode.Extract(50, 3); + } + else + { + mode = (XmadMode)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 XmadMode.Cfull: break; + + case XmadMode.Clo: srcC = Extend16To32(srcC, high: false, signed: false); break; + case XmadMode.Chi: srcC = Extend16To32(srcC, high: true, signed: false); break; + + case XmadMode.Cbcc: + { + srcC = context.IAdd(srcC, context.ShiftLeft(GetSrcB(context), Const(16))); + + break; + } + + case XmadMode.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 0000000000..b5bde1a1c1 --- /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 0000000000..a0aadf9b48 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs @@ -0,0 +1,200 @@ +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); + + Operand dest = GetDest(context); + + context.Copy(dest, srcB); + + SetZnFlags(context, dest, op.SetCondCode); + } + + 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); + + SetZnFlags(context, dest, op.SetCondCode); + } + + public static void I2F(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + FPType floatType = (FPType)op.RawOpCode.Extract(8, 2); + + IntegerType intType = (IntegerType)op.RawOpCode.Extract(10, 2); + + bool isSmallInt = intType <= 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 = intType == IntegerType.U16 ? 16 : 8; + + srcB = isSignedInt + ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size)); + } + + Operand dest = GetDest(context); + + if (isSignedInt) + { + context.Copy(dest, context.IConvertS32ToFP(srcB)); + } + else + { + context.Copy(dest, context.IConvertU32ToFP(srcB)); + } + } + + 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. + } + } +} \ 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 0000000000..e2f99e8358 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs @@ -0,0 +1,286 @@ +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 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.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 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))); + } + } + } +} \ 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 0000000000..1cd705d624 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs @@ -0,0 +1,100 @@ +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) + { + 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 0000000000..cfc2e94b70 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs @@ -0,0 +1,107 @@ +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(new Register(0, RegisterType.Flag)); + } + + public static Operand GetNF(EmitterContext context) + { + return Register(new Register(1, RegisterType.Flag)); + } + + public static Operand GetCF(EmitterContext context) + { + return Register(new Register(2, RegisterType.Flag)); + } + + public static Operand GetVF(EmitterContext context) + { + return Register(new 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) + { + return GetSrcB(context); + } + + 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); + } + + 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 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 0000000000..e333b5c37a --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs @@ -0,0 +1,457 @@ +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.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++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + 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 Tex(EmitterContext context) + { + OpCodeTex op = (OpCodeTex)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 Register(new Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(new Register(rbIndex++, RegisterType.Gpr)); + } + + Operand arrayIndex = op.IsArray ? Ra() : null; + + List sourcesList = new List(); + + switch (op.Dimensions) + { + case TextureDimensions.Texture1D: + sourcesList.Add(Ra()); + break; + + case TextureDimensions.Texture2D: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + break; + + case TextureDimensions.Texture3D: + case TextureDimensions.TextureCube: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + break; + } + + int elemsCount = sourcesList.Count; + + TextureType type = GetTextureType(op.Dimensions); + + 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()); + } + + if (op.LodMode == TextureLodMode.LodZero || + op.LodMode == TextureLodMode.LodLevel || + op.LodMode == TextureLodMode.LodLevelA) + { + sourcesList.Add(lodValue); + + type |= TextureType.LodLevel; + } + + if (op.HasOffset) + { + for (int index = 0; index < elemsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * 4), Const(4))); + } + + type |= TextureType.Offset; + } + + if (op.LodMode == TextureLodMode.LodBias || + op.LodMode == TextureLodMode.LodBiasA) + { + sourcesList.Add(lodValue); + + type |= TextureType.LodBias; + } + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(new Register(rdIndex++, RegisterType.Gpr)); + } + + int textureHandle = 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, + textureHandle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + public static void Texs(EmitterContext context) + { + OpCodeTexs op = (OpCodeTexs)context.CurrOp; + + if (op.Rd0.IsRZ && op.Rd1.IsRZ) + { + return; + } + + List sourcesList = new List(); + + Operand GetSource(int index) + { + int regIndex = 0; + + switch (index & 2) + { + case 0: regIndex = op.Ra.Index; break; + case 2: regIndex = op.Rb.Index; break; + } + + if (regIndex != RegisterConsts.RegisterZeroIndex) + { + regIndex += index & 1; + } + + return context.Copy(Register(new Register(regIndex, RegisterType.Gpr))); + } + + switch (op.Type) + { + case TexsType.Texture1DLodZero: + sourcesList.Add(GetSource(0)); + break; + + case TexsType.Texture2D: + sourcesList.Add(GetSource(0)); + sourcesList.Add(GetSource(2)); + break; + + case TexsType.Texture2DLodZero: + sourcesList.Add(GetSource(0)); + sourcesList.Add(GetSource(2)); + sourcesList.Add(ConstF(0)); + break; + + case TexsType.Texture2DLodLevel: + case TexsType.Texture2DDepthCompare: + case TexsType.Texture3D: + case TexsType.TextureCube: + sourcesList.Add(GetSource(0)); + sourcesList.Add(GetSource(1)); + sourcesList.Add(GetSource(2)); + break; + + case TexsType.Texture2DLodZeroDepthCompare: + case TexsType.Texture3DLodZero: + sourcesList.Add(GetSource(0)); + sourcesList.Add(GetSource(1)); + sourcesList.Add(GetSource(2)); + sourcesList.Add(ConstF(0)); + break; + + case TexsType.Texture2DLodLevelDepthCompare: + case TexsType.TextureCubeLodLevel: + sourcesList.Add(GetSource(0)); + sourcesList.Add(GetSource(1)); + sourcesList.Add(GetSource(2)); + sourcesList.Add(GetSource(3)); + break; + + case TexsType.Texture2DArray: + sourcesList.Add(GetSource(1)); + sourcesList.Add(GetSource(2)); + sourcesList.Add(GetSource(0)); + break; + + case TexsType.Texture2DArrayLodZero: + sourcesList.Add(GetSource(1)); + sourcesList.Add(GetSource(2)); + sourcesList.Add(GetSource(0)); + sourcesList.Add(ConstF(0)); + break; + + case TexsType.Texture2DArrayLodZeroDepthCompare: + sourcesList.Add(GetSource(1)); + sourcesList.Add(GetSource(2)); + sourcesList.Add(GetSource(0)); + sourcesList.Add(GetSource(3)); + sourcesList.Add(ConstF(0)); + break; + } + + Operand[] sources = sourcesList.ToArray(); + + TextureType type = GetTextureType(op.Type); + + int destIncrement = 0; + + Operand GetDest() + { + int rdIndex; + + if (op.Rd1.IsRZ) + { + rdIndex = op.Rd0.Index; + } + else if (op.Rd0.IsRZ) + { + rdIndex = op.Rd1.Index; + } + else + { + rdIndex = (destIncrement >> 1) != 0 ? op.Rd1.Index : op.Rd0.Index; + } + + rdIndex += destIncrement++ & 1; + + return Register(new Register(rdIndex, RegisterType.Gpr)); + } + + int textureHandle = 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, + textureHandle, + 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(TexsType type) + { + switch (type) + { + case TexsType.Texture1DLodZero: + return TextureType.Texture1D | TextureType.LodLevel; + + case TexsType.Texture2D: + return TextureType.Texture2D; + + case TexsType.Texture2DLodZero: + case TexsType.Texture2DLodLevel: + return TextureType.Texture2D | TextureType.LodLevel; + + case TexsType.Texture2DDepthCompare: + return TextureType.Texture2D | TextureType.DepthCompare; + + case TexsType.Texture2DLodLevelDepthCompare: + case TexsType.Texture2DLodZeroDepthCompare: + return TextureType.Texture2D | TextureType.LodLevelDepthCompare; + + case TexsType.Texture2DArray: + return TextureType.Texture2D | TextureType.Array; + + case TexsType.Texture2DArrayLodZero: + return TextureType.Texture2D | TextureType.Array | TextureType.LodLevel; + + case TexsType.Texture2DArrayLodZeroDepthCompare: + return TextureType.Texture2D | TextureType.Array | TextureType.LodLevelDepthCompare; + + case TexsType.Texture3D: + return TextureType.Texture3D; + + case TexsType.Texture3DLodZero: + return TextureType.Texture3D | TextureType.LodLevel; + + case TexsType.TextureCube: + return TextureType.TextureCube; + + case TexsType.TextureCubeLodLevel: + return TextureType.TextureCube | TextureType.LodLevel; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + } +} \ 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 0000000000..b74dbfd721 --- /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/InstEmitter.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs new file mode 100644 index 0000000000..91c740b684 --- /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/IntegerCondition.cs b/Ryujinx.Graphics/Shader/Instructions/IntegerCondition.cs new file mode 100644 index 0000000000..a1937c2f52 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/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/Instructions/Lop3Expression.cs b/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs new file mode 100644 index 0000000000..e55ed660a3 --- /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/Instructions/MufuOperation.cs b/Ryujinx.Graphics/Shader/Instructions/MufuOperation.cs new file mode 100644 index 0000000000..0fc67e7131 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/MufuOperation.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Instructions +{ + 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/Instructions/XmadMode.cs b/Ryujinx.Graphics/Shader/Instructions/XmadMode.cs new file mode 100644 index 0000000000..9d1757fa2b --- /dev/null +++ b/Ryujinx.Graphics/Shader/Instructions/XmadMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.Instructions +{ + enum XmadMode + { + Cfull = 0, + Clo = 1, + Chi = 2, + Csfu = 3, + Cbcc = 4 + } +} \ 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 0000000000..d0aaa28a4a --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class BasicBlock + { + public int Index { get; set; } + + public LinkedList Operations { get; } + + private BasicBlock _next; + + public BasicBlock Next + { + get => _next; + set => _next = AddSuccessor(_next, value); + } + + private BasicBlock _branch; + + public BasicBlock Branch + { + get => _branch; + set => _branch = AddSuccessor(_branch, value); + } + + 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 0000000000..48dda24b1e --- /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 0000000000..daa4a51e42 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,78 @@ +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, + ExponentB2, + Floor, + FusedMultiplyAdd, + IsNan, + LoadConstant, + LoadGlobal, + LoadLocal, + LogarithmB2, + MarkLabel, + Maximum, + MaximumU32, + Minimum, + MinimumU32, + Multiply, + Negate, + PackDouble2x32, + PackHalf2x16, + ReciprocalSquareRoot, + Return, + ShiftLeft, + ShiftRightS32, + ShiftRightU32, + Sine, + SquareRoot, + StoreGlobal, + StoreLocal, + Subtract, + TextureSample, + 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 0000000000..c264e47d1a --- /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 0000000000..a0e49bf25b --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs @@ -0,0 +1,73 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operand + { + private const int AttrSlotBits = 5; + private const int AttrSlotLsb = 32 - AttrSlotBits; + private const int AttrSlotMask = (1 << AttrSlotBits) - 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 << AttrSlotLsb) | offset; + } + + private static int PackRegInfo(int index, RegisterType type) + { + return ((int)type << 24) | index; + } + + public int GetCbufSlot() + { + return (Value >> AttrSlotLsb) & AttrSlotMask; + } + + public int GetCbufOffset() + { + return Value & ~(AttrSlotMask << AttrSlotLsb); + } + + public Register GetRegister() + { + return new Register(Value & 0xffffff, (RegisterType)(Value >> 24)); + } + } +} \ 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 0000000000..51e42b1f25 --- /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(Register reg, int increment) + { + return new Operand(new Register(reg.Index + increment, reg.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 0000000000..e0e2a6675a --- /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 0000000000..c4f3dbf1cd --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs @@ -0,0 +1,61 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operation : INode + { + public Instruction Inst { get; } + + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + private Operand[] _sources; + + public int SourcesCount => _sources.Length; + + public Operation(Instruction inst, Operand dest, params Operand[] sources) + { + Inst = inst; + Dest = dest; + _sources = sources; + + for (int index = 0; index < sources.Length; index++) + { + Operand source = sources[index]; + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + } + } + + 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 operand) + { + if (operand.Type == OperandType.LocalVariable) + { + operand.UseOps.Add(this); + } + + _sources[index] = operand; + } + } +} \ 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 0000000000..81609ea9f1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs @@ -0,0 +1,87 @@ +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 operand) + { + if (operand.Type == OperandType.LocalVariable) + { + operand.UseOps.Add(this); + } + + _sources[index].Operand = operand; + } + } +} \ 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 0000000000..cc417efb07 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class TextureOperation : Operation + { + public TextureType Type { get; } + + public int TextureHandle { get; } + public int ComponentIndex { get; } + + public TextureOperation( + Instruction inst, + TextureType type, + int textureHandle, + int componentIndex, + Operand dest, + params Operand[] sources) : base(inst, dest, sources) + { + Type = type; + TextureHandle = textureHandle; + ComponentIndex = componentIndex; + } + } +} \ 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 0000000000..54ae2a9995 --- /dev/null +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum TextureType + { + Texture1D, + Texture2D, + Texture3D, + TextureCube, + + Mask = 0xff, + + Array = 1 << 8, + DepthCompare = 1 << 9, + LodBias = 1 << 10, + LodLevel = 1 << 11, + Offset = 1 << 12, + + LodLevelDepthCompare = LodLevel | DepthCompare + } +} \ 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 0000000000..53abdc56ed --- /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/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs new file mode 100644 index 0000000000..20847b3028 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstAssignment : IAstNode + { + public IAstNode Destination { get; } + public IAstNode Source { get; } + + public AstAssignment(IAstNode destination, IAstNode source) + { + Destination = destination; + Source = source; + } + } +} \ 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 0000000000..62a998a028 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlock : IAstNode + { + public AstBlockType Type { get; } + + public IAstNode Condition { get; } + + public LinkedList Nodes { get; } + + public AstBlock(AstBlockType type, IAstNode condition = null) + { + Type = type; + + Condition = condition; + + Nodes = new LinkedList(); + } + } +} \ 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 0000000000..b607a3b21c --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum AstBlockType + { + DoWhile, + If, + Else, + Main, + While + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs new file mode 100644 index 0000000000..0c75c67ca1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstDeclaration : IAstNode + { + public AstOperand Operand { get; } + + public AstDeclaration(AstOperand operand) + { + Operand = operand; + } + } +} \ 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 0000000000..90332a8041 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs @@ -0,0 +1,42 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperand : IAstNode + { + public OperandType Type { get; } + + public VariableType VarType { get; set; } + + public int Value { get; } + + public int CbufSlot { get; } + public int CbufOffset { get; } + + private AstOperand() + { + 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) : 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 0000000000..8bdf4b8645 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs @@ -0,0 +1,17 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperation : IAstNode + { + public Instruction Inst { get; } + + public IAstNode[] Sources { get; } + + public AstOperation(Instruction inst, params IAstNode[] sources) + { + Inst = inst; + Sources = sources; + } + } +} \ 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 0000000000..9c8befe60d --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstTextureOperation : AstOperation + { + public TextureType Type { get; } + + public int TextureHandle { get; } + public int[] Components { get; } + + public AstTextureOperation( + Instruction inst, + TextureType type, + int textureHandle, + int[] components, + params IAstNode[] sources) : base(inst, sources) + { + Type = type; + TextureHandle = textureHandle; + Components = components; + } + } +} \ 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 0000000000..fcb8723df5 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + interface IAstNode + { + + } +} \ 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 0000000000..51a6abed92 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs @@ -0,0 +1,124 @@ +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.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.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Truncate, VariableType.F32, VariableType.F32); + } + + private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes) + { + _infoTbl[(int)inst] = new InstInfo(destType, srcTypes); + } + + public static VariableType GetDestVarType(Instruction inst) + { + if (IsTextureInst(inst)) + { + return VariableType.F32; + } + + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst); + } + + public static VariableType GetSrcVarType(Instruction inst, int index) + { + 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; + } + + private static bool IsTextureInst(Instruction inst) + { + return inst == Instruction.TextureSample; + } + } +} \ 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 0000000000..a3a8d13839 --- /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/StructuredProgram.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs new file mode 100644 index 0000000000..0d1c16fa67 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs @@ -0,0 +1,205 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class StructuredProgram + { + public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks) + { + RemovePhis(blocks); + + StructuredProgramContext context = new StructuredProgramContext(blocks); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + context.EnterBlock(block); + + foreach (INode node in block.Operations) + { + if (node is Operation operation) + { + Instruction inst = operation.Inst; + + if (operation.Dest != null && + operation.Inst != Instruction.MarkLabel && + operation.Inst != Instruction.Branch && + operation.Inst != Instruction.BranchIfTrue && + operation.Inst != Instruction.BranchIfFalse) + { + AstOperand dest = context.GetOperandDef(operation.Dest); + + IAstNode[] sources = new IAstNode[operation.SourcesCount]; + + for (int index = 0; index < sources.Length; index++) + { + sources[index] = context.GetOperandUse(operation.GetSource(index)); + } + + if (inst == Instruction.LoadConstant) + { + context.Info.ConstantBuffers.Add((sources[0] as AstOperand).Value); + } + + AstAssignment astAsg; + + if (inst == Instruction.Copy) + { + //Copies are pretty much a typeless operation, + //so it's better to get the type from the source + //operand used on the copy, to avoid unnecessary + //reinterpret casts on the generated code. + dest.VarType = GetVarTypeFromUses(operation.Dest); + + astAsg = new AstAssignment(dest, sources[0]); + } + else + { + dest.VarType = InstructionInfo.GetDestVarType(inst); + + AstOperation astOperation; + + if (operation is TextureOperation texOp) + { + if (!context.Info.Samplers.TryAdd(texOp.TextureHandle, texOp.Type)) + { + //TODO: Warning. + } + + int[] components = new int[] { texOp.ComponentIndex }; + + astOperation = new AstTextureOperation( + inst, + texOp.Type, + texOp.TextureHandle, + components, + sources); + } + else + { + astOperation = new AstOperation(inst, sources); + } + + astAsg = new AstAssignment(dest, astOperation); + } + + context.AddNode(astAsg); + } + else + { + //If dest is null, it's assumed that all the source + //operands are also null. + AstOperation astOperation = new AstOperation(inst); + + context.AddNode(astOperation); + } + } + } + + context.LeaveBlock(block); + } + + context.PrependLocalDeclarations(); + + return context.Info; + } + + private static void RemovePhis(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; + } + + private static VariableType GetVarTypeFromUses(Operand dest) + { + foreach (INode useNode in dest.UseOps) + { + if (useNode is Operation operation) + { + if (operation.Inst == Instruction.Copy) + { + if (operation.Dest.Type == OperandType.LocalVariable) + { + //TODO: Search through copies. + return VariableType.S32; + } + + return OperandInfo.GetVarType(operation.Dest.Type); + } + + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index) == dest) + { + return InstructionInfo.GetSrcVarType(operation.Inst, index); + } + } + } + } + + return VariableType.S32; + } + } +} \ 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 0000000000..863c2771bb --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs @@ -0,0 +1,311 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramContext + { + private BasicBlock[] _blocks; + + private List<(AstBlock Block, int EndIndex)> _blockStack; + + private Dictionary _locals; + + private AstBlock _currBlock; + + private int _currEndIndex; + + public StructuredProgramInfo Info { get; } + + public StructuredProgramContext(BasicBlock[] blocks) + { + _blocks = blocks; + + _blockStack = new List<(AstBlock, int)>(); + + _locals = new Dictionary(); + + _currBlock = new AstBlock(AstBlockType.Main); + + _currEndIndex = blocks.Length; + + Info = new StructuredProgramInfo(_currBlock); + } + + public void EnterBlock(BasicBlock block) + { + while (_currEndIndex == block.Index) + { + PopBlock(); + } + + LookForDoWhileStatements(block); + } + + public void LeaveBlock(BasicBlock block) + { + LookForIfElseStatements(block); + } + + private void LookForDoWhileStatements(BasicBlock block) + { + //Check if we have any predecessor whose index is greater than the + //current block, this indicates a loop. + foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index)) + { + if (predecessor.Index < block.Index) + { + break; + } + + if (predecessor.Index < _currEndIndex) + { + Operation branchOp = (Operation)predecessor.GetLastOp(); + + NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1); + } + else /* if (predecessor.Index >= _currEndIndex) */ + { + //TODO: Handle unstructured case. + } + } + } + + private void LookForIfElseStatements(BasicBlock block) + { + if (block.Branch == null || block.Branch.Index <= block.Index) + { + return; + } + + Operation branchOp = (Operation)block.GetLastOp(); + + if (block.Branch.Index <= _currEndIndex) + { + //If (conditional branch forward). + NewBlock(AstBlockType.If, branchOp, block.Branch.Index); + } + else if (IsElseSkipBlock(block)) + { + //Else (unconditional branch forward). + int topBlockIndex = TopBlockIndexOnStack(); + + //We need to pop enough elements so that the one at + //"topBlockIndex" is the last one poped from the stack. + while (_blockStack.Count > topBlockIndex) + { + PopBlock(); + } + + NewBlock(AstBlockType.Else, branchOp, block.Branch.Index); + } + else if (IsElseSkipBlock(_blocks[_currEndIndex - 1]) && block.Branch == _blocks[_currEndIndex - 1].Branch) + { + //If (conditional branch forward). + NewBlock(AstBlockType.If, branchOp, _currEndIndex); + } + else if (block.Branch.Index > _currEndIndex) + { + //TODO: Handle unstructured case. + } + } + + private bool IsElseSkipBlock(BasicBlock block) + { + //Checks performed (in order): + //- The block should end with a branch. + //- The branch should be unconditional. + //- This should be the last block on the current (if) statement. + //- The statement before the else must be an if statement. + //- The branch target must be before or at (but not after) the end of the encloding block. + if (block.Branch == null || block.Next != null || block.Index + 1 != _currEndIndex) + { + return false; + } + + (AstBlock parentBlock, int parentEndIndex) = _blockStack[TopBlockIndexOnStack()]; + + if ((parentBlock.Nodes.Last.Value as AstBlock).Type != AstBlockType.If) + { + return false; + } + + return block.Branch.Index <= parentEndIndex; + } + + private void NewBlock(AstBlockType type, Operation branchOp, int endIndex) + { + Instruction inst = branchOp.Inst; + + if (type == AstBlockType.If) + { + //For ifs, the if block is only executed executed if the + //branch is not taken, that is, if the condition is false. + //So, we invert the condition to take that into account. + if (inst == Instruction.BranchIfFalse) + { + inst = Instruction.BranchIfTrue; + } + else if (inst == Instruction.BranchIfTrue) + { + inst = Instruction.BranchIfFalse; + } + } + + IAstNode cond; + + switch (inst) + { + case Instruction.Branch: + cond = new AstOperand(OperandType.Constant, IrConsts.True); + break; + + case Instruction.BranchIfFalse: + cond = new AstOperation(Instruction.BitwiseNot, GetOperandUse(branchOp.GetSource(0))); + break; + + case Instruction.BranchIfTrue: + cond = GetOperandUse(branchOp.GetSource(0)); + break; + + default: throw new InvalidOperationException($"Invalid branch instruction \"{branchOp.Inst}\"."); + } + + AstBlock childBlock = new AstBlock(type, cond); + + AddNode(childBlock); + + PushBlock(); + + _currBlock = childBlock; + + _currEndIndex = endIndex; + } + + public void AddNode(IAstNode node) + { + _currBlock.Nodes.AddLast(node); + } + + private void PushBlock() + { + _blockStack.Add((_currBlock, _currEndIndex)); + } + + private void PopBlock() + { + int lastIndex = _blockStack.Count - 1; + + (_currBlock, _currEndIndex) = _blockStack[lastIndex]; + + _blockStack.RemoveAt(lastIndex); + } + + private int TopBlockIndexOnStack() + { + for (int index = _blockStack.Count - 1; index >= 0; index--) + { + if (_blockStack[index].EndIndex > _currEndIndex) + { + return index; + } + } + + return 0; + } + + public void PrependLocalDeclarations() + { + AstBlock mainBlock = Info.MainBlock; + + LinkedListNode declNode = null; + + foreach (AstOperand operand in _locals.Values) + { + AstDeclaration astDecl = new AstDeclaration(operand); + + if (declNode == null) + { + declNode = mainBlock.Nodes.AddFirst(astDecl); + } + else + { + declNode = mainBlock.Nodes.AddAfter(declNode, astDecl); + } + } + } + + 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.ConstantBuffers.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 (!_locals.TryGetValue(operand, out AstOperand astOperand)) + { + astOperand = new AstOperand(operand); + + _locals.Add(operand, 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 0000000000..e7dc799d95 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramInfo + { + public AstBlock MainBlock { get; } + + public HashSet ConstantBuffers { get; } + + public HashSet IAttributes { get; } + public HashSet OAttributes { get; } + + public Dictionary Samplers { get; } + + public StructuredProgramInfo(AstBlock mainBlock) + { + MainBlock = mainBlock; + + ConstantBuffers = new HashSet(); + + IAttributes = new HashSet(); + OAttributes = new HashSet(); + + Samplers = new Dictionary(); + } + } +} \ 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 0000000000..4c7f384978 --- /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/Translation/AttributeConsts.cs b/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs new file mode 100644 index 0000000000..ae3e361c72 --- /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 0000000000..e2ca74a4de --- /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 0000000000..c9ff7a16eb --- /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 changed; + + do + { + changed = 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; + + changed = true; + } + } + } + while (changed); + } + + 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 0000000000..cb3caa6f88 --- /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(new 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(new 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 0000000000..577be9f482 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs @@ -0,0 +1,386 @@ +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 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 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); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizer.cs b/Ryujinx.Graphics/Shader/Translation/Optimizer.cs new file mode 100644 index 0000000000..efb24256d1 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Optimizer.cs @@ -0,0 +1,116 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + 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; + } + + if (operation.Inst == Instruction.Copy && DestIsLocalVar(operation)) + { + PropagateCopy(operation); + + RemoveNode(block, node); + + modified = true; + } + + node = nextNode; + } + } + } + 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); + + foreach (INode useNode in dest.UseOps) + { + for (int index = 0; index < useNode.SourcesCount; index++) + { + if (useNode.GetSource(index) == dest) + { + useNode.SetSource(index, src); + } + } + } + } + + 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/Ssa.cs b/Ryujinx.Graphics/Shader/Translation/Ssa.cs new file mode 100644 index 0000000000..b612649ca3 --- /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 0000000000..29fe3c55d2 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Translation/Translator.cs @@ -0,0 +1,109 @@ +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 static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public static class Translator + { + public static string Translate(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); + } + } + } + + BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(context.GetOperations()); + + Dominance.FindDominators(irBlocks[0], irBlocks.Length); + + Dominance.FindDominanceFrontiers(irBlocks); + + Ssa.Rename(irBlocks); + + Optimizer.Optimize(irBlocks); + + StructuredProgramInfo prgInfo = StructuredProgram.MakeStructuredProgram(irBlocks); + + GlslGenerator generator = new GlslGenerator(); + + string glslProgram = generator.Generate(prgInfo, shaderType); + + return glslProgram; + } + } +} \ No newline at end of file diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs index 77aba0abe6..ad29b6ffe2 100644 --- a/Ryujinx.ShaderTools/Program.cs +++ b/Ryujinx.ShaderTools/Program.cs @@ -1,5 +1,5 @@ using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Gal.Shader; +using Ryujinx.Graphics.Shader.Translation; using System; using System.IO; @@ -13,7 +13,7 @@ namespace Ryujinx.ShaderTools { if (args.Length == 2) { - GlslDecompiler Decompiler = new GlslDecompiler(MaxUboSize, true); + //GlslDecompiler Decompiler = new GlslDecompiler(MaxUboSize); GalShaderType ShaderType = GalShaderType.Vertex; @@ -30,9 +30,11 @@ namespace Ryujinx.ShaderTools { Memory Mem = new Memory(FS); - GlslProgram Program = Decompiler.Decompile(Mem, 0, ShaderType); + Translator.Translate(Mem, 0, ShaderType); - Console.WriteLine(Program.Code); + //GlslProgram Program = Decompiler.Decompile(Mem, 0, ShaderType); + + //Console.WriteLine(Program.Code); } } else diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 1b26b39188..42a6a74159 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"); }