aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2019-04-17 20:57:08 -0300
committerjduncanator <1518948+jduncanator@users.noreply.github.com>2019-04-18 09:57:08 +1000
commit6b23a2c125b9c48b5ebea92716004ef68698bb0f (patch)
tree69332df6fbbd8e2bddc522ba682fcc5c7a69e101
parentb2e88b04a85b41cc60af3485d88c90429e84a218 (diff)
New shader translator implementation (#654)
* Start implementing a new shader translator * Fix shift instructions and a typo * Small refactoring on StructuredProgram, move RemovePhis method to a separate class * Initial geometry shader support * Implement TLD4 * Fix -- There's no negation on FMUL32I * Add constant folding and algebraic simplification optimizations, nits * Some leftovers from constant folding * Avoid cast for constant assignments * Add a branch elimination pass, and misc small fixes * Remove redundant branches, add expression propagation and other improvements on the code * Small leftovers -- add missing break and continue, remove unused properties, other improvements * Add null check to handle empty block cases on block visitor * Add HADD2 and HMUL2 half float shader instructions * Optimize pack/unpack sequences, some fixes related to half float instructions * Add TXQ, TLD, TLDS and TLD4S shader texture instructions, and some support for bindless textures, some refactoring on codegen * Fix copy paste mistake that caused RZ to be ignored on the AST instruction * Add workaround for conditional exit, and fix half float instruction with constant buffer * Add missing 0.0 source for TLDS.LZ variants * Simplify the switch for TLDS.LZ * Texture instructions related fixes * Implement the HFMA instruction, and some misc. fixes * Enable constant folding on UnpackHalf2x16 instructions * Refactor HFMA to use OpCode* for opcode decoding rather than on the helper methods * Remove the old shader translator * Remove ShaderDeclInfo and other unused things * Add dual vertex shader support * Add ShaderConfig, used to pass shader type and maximum cbuffer size * Move and rename some instruction enums * Move texture instructions into a separate file * Move operand GetExpression and locals management to OperandManager * Optimize opcode decoding using a simple list and binary search * Add missing condition for do-while on goto elimination * Misc. fixes on texture instructions * Simplify TLDS switch * Address PR feedback, and a nit
-rw-r--r--Ryujinx.Graphics/Gal/IGalShader.cs5
-rw-r--r--Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs5
-rw-r--r--Ryujinx.Graphics/Gal/OpenGL/OglShader.cs51
-rw-r--r--Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs13
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslDecl.cs420
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs1679
-rw-r--r--Ryujinx.Graphics/Gal/Shader/GlslProgram.cs22
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs1299
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs57
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs4
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs78
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs878
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs431
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs313
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs25
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs218
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs146
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs10
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs14
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs46
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs12
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs17
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs94
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs4
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs12
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs24
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs15
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs4
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs25
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs15
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs17
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs36
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs12
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs12
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs17
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs190
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderOper.cs11
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderRegisterSize.cs9
-rw-r--r--Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs13
-rw-r--r--Ryujinx.Graphics/Gal/ShaderDeclInfo.cs45
-rw-r--r--Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs25
-rw-r--r--Ryujinx.Graphics/Shader/CBufferDescriptor.cs15
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs90
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs206
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs17
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs133
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs20
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs110
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs170
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs244
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs45
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs18
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs27
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs104
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs239
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs85
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs25
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/Block.cs117
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/Condition.cs45
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/ConditionalOperation.cs10
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/Decoder.cs406
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs58
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs10
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/FPType.cs9
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/FmulScale.cs13
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCode.cs16
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs12
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeCbuf.cs8
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs12
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs13
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs7
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeImmF.cs7
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeLop.cs10
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs7
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs7
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs7
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeReg.cs7
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs8
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs18
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs9
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs9
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs12
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/IntegerType.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs10
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs15
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCode.cs30
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs34
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAluCbuf.cs16
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs18
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAluReg.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAluRegCbuf.cs18
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeAttribute.cs16
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs19
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs24
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeFArithCbuf.cs16
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs30
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeFArithReg.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs16
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs22
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs30
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs26
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs25
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs29
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs30
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs26
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs28
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeLopCbuf.cs16
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm32.cs22
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeLopReg.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs20
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs26
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeSetCbuf.cs16
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeSetImm.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeSetReg.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs20
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs15
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs216
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs14
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs11
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs42
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs61
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs20
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs20
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs20
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs11
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/Register.cs36
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/RegisterConsts.cs13
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/RegisterType.cs9
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs10
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs15
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs10
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs9
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs12
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs13
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs20
-rw-r--r--Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs11
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs684
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs88
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs213
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs369
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs107
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs267
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs138
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs32
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs776
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs6
-rw-r--r--Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs149
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs61
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/INode.cs13
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs87
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs8
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs79
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs62
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs15
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs101
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs94
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs17
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs24
-rw-r--r--Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs35
-rw-r--r--Ryujinx.Graphics/Shader/ShaderConfig.cs23
-rw-r--r--Ryujinx.Graphics/Shader/ShaderHeader.cs166
-rw-r--r--Ryujinx.Graphics/Shader/ShaderProgram.cs15
-rw-r--r--Ryujinx.Graphics/Shader/ShaderProgramInfo.cs17
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs35
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs116
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs12
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs68
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs73
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs11
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs49
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs49
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs149
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs25
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs459
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs23
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs11
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs142
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs34
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs74
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs254
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs292
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs32
-rw-r--r--Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs13
-rw-r--r--Ryujinx.Graphics/Shader/TextureDescriptor.cs36
-rw-r--r--Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs30
-rw-r--r--Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs108
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Dominance.cs127
-rw-r--r--Ryujinx.Graphics/Shader/Translation/EmitterContext.cs105
-rw-r--r--Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs420
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs64
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs323
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs47
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs172
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs147
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Ssa.cs330
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Translator.cs219
-rw-r--r--Ryujinx.Graphics/Texture/TextureInstructionSuffix.cs19
-rw-r--r--Ryujinx.ShaderTools/Memory.cs2
-rw-r--r--Ryujinx.ShaderTools/Program.cs31
-rw-r--r--Ryujinx/Program.cs2
207 files changed, 11514 insertions, 6311 deletions
diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs
index 99cd4d76..6a9abe75 100644
--- a/Ryujinx.Graphics/Gal/IGalShader.cs
+++ b/Ryujinx.Graphics/Gal/IGalShader.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Graphics.Shader;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal
@@ -8,8 +9,8 @@ namespace Ryujinx.Graphics.Gal
void Create(IGalMemory memory, long vpAPos, long key, GalShaderType type);
- IEnumerable<ShaderDeclInfo> GetConstBufferUsage(long key);
- IEnumerable<ShaderDeclInfo> GetTextureUsage(long key);
+ IEnumerable<CBufferDescriptor> GetConstBufferUsage(long key);
+ IEnumerable<TextureDescriptor> GetTextureUsage(long key);
void Bind(long key);
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs
index 3c8ada3e..64768e28 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs
@@ -1,4 +1,5 @@
using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic;
@@ -529,9 +530,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
if (stage != null)
{
- foreach (ShaderDeclInfo declInfo in stage.ConstBufferUsage)
+ foreach (CBufferDescriptor desc in stage.ConstBufferUsage)
{
- long key = New.ConstBufferKeys[(int)stage.Type][declInfo.Cbuf];
+ long key = New.ConstBufferKeys[(int)stage.Type][desc.Slot];
if (key != 0 && _buffer.TryGetUbo(key, out int uboHandle))
{
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs
index 8faa9053..8f4072c5 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs
@@ -1,5 +1,6 @@
using OpenTK.Graphics.OpenGL;
-using Ryujinx.Graphics.Gal.Shader;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -51,54 +52,54 @@ namespace Ryujinx.Graphics.Gal.OpenGL
bool isDualVp,
GalShaderType type)
{
- GlslProgram program;
+ ShaderConfig config = new ShaderConfig(type, OglLimit.MaxUboSize);
- GlslDecompiler decompiler = new GlslDecompiler(OglLimit.MaxUboSize, OglExtension.NvidiaDriver);
-
- int shaderDumpIndex = ShaderDumper.DumpIndex;
+ ShaderProgram program;
if (isDualVp)
{
ShaderDumper.Dump(memory, position, type, "a");
ShaderDumper.Dump(memory, positionB, type, "b");
- program = decompiler.Decompile(memory, position, positionB, type);
+ program = Translator.Translate(memory, (ulong)position, (ulong)positionB, config);
}
else
{
ShaderDumper.Dump(memory, position, type);
- program = decompiler.Decompile(memory, position, type);
+ program = Translator.Translate(memory, (ulong)position, config);
}
string code = program.Code;
if (ShaderDumper.IsDumpEnabled())
{
+ int shaderDumpIndex = ShaderDumper.DumpIndex;
+
code = "//Shader " + shaderDumpIndex + Environment.NewLine + code;
}
- return new OglShaderStage(type, code, program.Uniforms, program.Textures);
+ return new OglShaderStage(type, code, program.Info.CBuffers, program.Info.Textures);
}
- public IEnumerable<ShaderDeclInfo> GetConstBufferUsage(long key)
+ public IEnumerable<CBufferDescriptor> GetConstBufferUsage(long key)
{
if (_stages.TryGetValue(key, out OglShaderStage stage))
{
return stage.ConstBufferUsage;
}
- return Enumerable.Empty<ShaderDeclInfo>();
+ return Enumerable.Empty<CBufferDescriptor>();
}
- public IEnumerable<ShaderDeclInfo> GetTextureUsage(long key)
+ public IEnumerable<TextureDescriptor> GetTextureUsage(long key)
{
if (_stages.TryGetValue(key, out OglShaderStage stage))
{
return stage.TextureUsage;
}
- return Enumerable.Empty<ShaderDeclInfo>();
+ return Enumerable.Empty<TextureDescriptor>();
}
public unsafe void SetExtraData(float flipX, float flipY, int instance)
@@ -130,16 +131,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
private void Bind(OglShaderStage stage)
{
- if (stage.Type == GalShaderType.Geometry)
- {
- //Enhanced layouts are required for Geometry shaders
- //skip this stage if current driver has no ARB_enhanced_layouts
- if (!OglExtension.EnhancedLayouts)
- {
- return;
- }
- }
-
switch (stage.Type)
{
case GalShaderType.Vertex: Current.Vertex = stage; break;
@@ -221,7 +212,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
private void BindUniformBlocks(int programHandle)
{
- int extraBlockindex = GL.GetUniformBlockIndex(programHandle, GlslDecl.ExtraUniformBlockName);
+ int extraBlockindex = GL.GetUniformBlockIndex(programHandle, "Extra");
GL.UniformBlockBinding(programHandle, extraBlockindex, 0);
@@ -231,14 +222,16 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
if (stage != null)
{
- foreach (ShaderDeclInfo declInfo in stage.ConstBufferUsage)
+ foreach (CBufferDescriptor desc in stage.ConstBufferUsage)
{
- int blockIndex = GL.GetUniformBlockIndex(programHandle, declInfo.Name);
+ int blockIndex = GL.GetUniformBlockIndex(programHandle, desc.Name);
if (blockIndex < 0)
{
- //It is expected that its found, if it's not then driver might be in a malfunction
- throw new InvalidOperationException();
+ //This may be fine, the compiler may optimize away unused uniform buffers,
+ //and in this case the above call would return -1 as the buffer has been
+ //optimized away.
+ continue;
}
GL.UniformBlockBinding(programHandle, blockIndex, freeBinding);
@@ -263,9 +256,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
if (stage != null)
{
- foreach (ShaderDeclInfo decl in stage.TextureUsage)
+ foreach (TextureDescriptor desc in stage.TextureUsage)
{
- int location = GL.GetUniformLocation(programHandle, decl.Name);
+ int location = GL.GetUniformLocation(programHandle, desc.Name);
GL.Uniform1(location, index);
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs b/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs
index 9e68a8e6..86126bca 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs
@@ -1,4 +1,5 @@
using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic;
@@ -23,14 +24,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public string Code { get; private set; }
- public IEnumerable<ShaderDeclInfo> ConstBufferUsage { get; private set; }
- public IEnumerable<ShaderDeclInfo> TextureUsage { get; private set; }
+ public IEnumerable<CBufferDescriptor> ConstBufferUsage { get; private set; }
+ public IEnumerable<TextureDescriptor> TextureUsage { get; private set; }
public OglShaderStage(
- GalShaderType type,
- string code,
- IEnumerable<ShaderDeclInfo> constBufferUsage,
- IEnumerable<ShaderDeclInfo> textureUsage)
+ GalShaderType type,
+ string code,
+ IEnumerable<CBufferDescriptor> constBufferUsage,
+ IEnumerable<TextureDescriptor> textureUsage)
{
Type = type;
Code = code;
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
deleted file mode 100644
index 73426762..00000000
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
+++ /dev/null
@@ -1,420 +0,0 @@
-using Ryujinx.Graphics.Texture;
-using System;
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class GlslDecl
- {
- public const int LayerAttr = 0x064;
- public const int PointSizeAttr = 0x06c;
- public const int PointCoordAttrX = 0x2e0;
- public const int PointCoordAttrY = 0x2e4;
- public const int TessCoordAttrX = 0x2f0;
- public const int TessCoordAttrY = 0x2f4;
- public const int TessCoordAttrZ = 0x2f8;
- public const int InstanceIdAttr = 0x2f8;
- public const int VertexIdAttr = 0x2fc;
- public const int FaceAttr = 0x3fc;
-
- public const int GlPositionVec4Index = 7;
-
- public const int PositionOutAttrLocation = 15;
-
- private const int AttrStartIndex = 8;
- private const int TexStartIndex = 8;
-
- public const string PositionOutAttrName = "position";
-
- private const string TextureName = "tex";
- private const string UniformName = "c";
-
- private const string AttrName = "attr";
- private const string InAttrName = "in_" + AttrName;
- private const string OutAttrName = "out_" + AttrName;
-
- private const string GprName = "gpr";
- private const string PredName = "pred";
-
- public const string FragmentOutputName = "FragColor";
-
- public const string ExtraUniformBlockName = "Extra";
- public const string FlipUniformName = "flip";
- public const string InstanceUniformName = "instance";
-
- public const string BasicBlockName = "bb";
- public const string BasicBlockAName = BasicBlockName + "_a";
- public const string BasicBlockBName = BasicBlockName + "_b";
-
- public const int SsyStackSize = 16;
- public const string SsyStackName = "ssy_stack";
- public const string SsyCursorName = "ssy_cursor";
-
- private string[] _stagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" };
-
- private string _stagePrefix;
-
- private Dictionary<ShaderIrOp, ShaderDeclInfo> m_CbTextures;
-
- private Dictionary<int, ShaderDeclInfo> m_Textures;
- private Dictionary<int, ShaderDeclInfo> m_Uniforms;
-
- private Dictionary<int, ShaderDeclInfo> m_Attributes;
- private Dictionary<int, ShaderDeclInfo> m_InAttributes;
- private Dictionary<int, ShaderDeclInfo> m_OutAttributes;
-
- private Dictionary<int, ShaderDeclInfo> m_Gprs;
- private Dictionary<int, ShaderDeclInfo> m_GprsHalf;
- private Dictionary<int, ShaderDeclInfo> m_Preds;
-
- public IReadOnlyDictionary<ShaderIrOp, ShaderDeclInfo> CbTextures => m_CbTextures;
-
- public IReadOnlyDictionary<int, ShaderDeclInfo> Textures => m_Textures;
- public IReadOnlyDictionary<int, ShaderDeclInfo> Uniforms => m_Uniforms;
-
- public IReadOnlyDictionary<int, ShaderDeclInfo> Attributes => m_Attributes;
- public IReadOnlyDictionary<int, ShaderDeclInfo> InAttributes => m_InAttributes;
- public IReadOnlyDictionary<int, ShaderDeclInfo> OutAttributes => m_OutAttributes;
-
- public IReadOnlyDictionary<int, ShaderDeclInfo> Gprs => m_Gprs;
- public IReadOnlyDictionary<int, ShaderDeclInfo> GprsHalf => m_GprsHalf;
- public IReadOnlyDictionary<int, ShaderDeclInfo> Preds => m_Preds;
-
- public GalShaderType ShaderType { get; private set; }
-
- private GlslDecl(GalShaderType shaderType)
- {
- ShaderType = shaderType;
-
- m_CbTextures = new Dictionary<ShaderIrOp, ShaderDeclInfo>();
-
- m_Textures = new Dictionary<int, ShaderDeclInfo>();
- m_Uniforms = new Dictionary<int, ShaderDeclInfo>();
-
- m_Attributes = new Dictionary<int, ShaderDeclInfo>();
- m_InAttributes = new Dictionary<int, ShaderDeclInfo>();
- m_OutAttributes = new Dictionary<int, ShaderDeclInfo>();
-
- m_Gprs = new Dictionary<int, ShaderDeclInfo>();
- m_GprsHalf = new Dictionary<int, ShaderDeclInfo>();
- m_Preds = new Dictionary<int, ShaderDeclInfo>();
- }
-
- public GlslDecl(ShaderIrBlock[] blocks, GalShaderType shaderType, ShaderHeader header) : this(shaderType)
- {
- _stagePrefix = _stagePrefixes[(int)shaderType] + "_";
-
- if (shaderType == GalShaderType.Fragment)
- {
- int index = 0;
-
- for (int attachment = 0; attachment < 8; attachment++)
- {
- for (int component = 0; component < 4; component++)
- {
- if (header.OmapTargets[attachment].ComponentEnabled(component))
- {
- m_Gprs.TryAdd(index, new ShaderDeclInfo(GetGprName(index), index));
-
- index++;
- }
- }
- }
-
- if (header.OmapDepth)
- {
- index = header.DepthRegister;
-
- m_Gprs.TryAdd(index, new ShaderDeclInfo(GetGprName(index), index));
- }
- }
-
- foreach (ShaderIrBlock block in blocks)
- {
- ShaderIrNode[] nodes = block.GetNodes();
-
- foreach (ShaderIrNode node in nodes)
- {
- Traverse(nodes, null, node);
- }
- }
- }
-
- public static GlslDecl Merge(GlslDecl vpA, GlslDecl vpB)
- {
- GlslDecl combined = new GlslDecl(GalShaderType.Vertex);
-
- Merge(combined.m_Textures, vpA.m_Textures, vpB.m_Textures);
- Merge(combined.m_Uniforms, vpA.m_Uniforms, vpB.m_Uniforms);
-
- Merge(combined.m_Attributes, vpA.m_Attributes, vpB.m_Attributes);
- Merge(combined.m_OutAttributes, vpA.m_OutAttributes, vpB.m_OutAttributes);
-
- Merge(combined.m_Gprs, vpA.m_Gprs, vpB.m_Gprs);
- Merge(combined.m_GprsHalf, vpA.m_GprsHalf, vpB.m_GprsHalf);
- Merge(combined.m_Preds, vpA.m_Preds, vpB.m_Preds);
-
- //Merge input attributes.
- foreach (KeyValuePair<int, ShaderDeclInfo> kv in vpA.m_InAttributes)
- {
- combined.m_InAttributes.TryAdd(kv.Key, kv.Value);
- }
-
- foreach (KeyValuePair<int, ShaderDeclInfo> kv in vpB.m_InAttributes)
- {
- //If Vertex Program A already writes to this attribute,
- //then we don't need to add it as an input attribute since
- //Vertex Program A will already have written to it anyway,
- //and there's no guarantee that there is an input attribute
- //for this slot.
- if (!vpA.m_OutAttributes.ContainsKey(kv.Key))
- {
- combined.m_InAttributes.TryAdd(kv.Key, kv.Value);
- }
- }
-
- return combined;
- }
-
- public static string GetGprName(int index)
- {
- return GprName + index;
- }
-
- private static void Merge(
- Dictionary<int, ShaderDeclInfo> c,
- Dictionary<int, ShaderDeclInfo> a,
- Dictionary<int, ShaderDeclInfo> b)
- {
- foreach (KeyValuePair<int, ShaderDeclInfo> kv in a)
- {
- c.TryAdd(kv.Key, kv.Value);
- }
-
- foreach (KeyValuePair<int, ShaderDeclInfo> kv in b)
- {
- c.TryAdd(kv.Key, kv.Value);
- }
- }
-
- private void Traverse(ShaderIrNode[] nodes, ShaderIrNode parent, ShaderIrNode node)
- {
- switch (node)
- {
- case ShaderIrAsg asg:
- {
- Traverse(nodes, asg, asg.Dst);
- Traverse(nodes, asg, asg.Src);
-
- break;
- }
-
- case ShaderIrCond cond:
- {
- Traverse(nodes, cond, cond.Pred);
- Traverse(nodes, cond, cond.Child);
-
- break;
- }
-
- case ShaderIrOp op:
- {
- Traverse(nodes, op, op.OperandA);
- Traverse(nodes, op, op.OperandB);
- Traverse(nodes, op, op.OperandC);
-
- if (op.Inst == ShaderIrInst.Texq ||
- op.Inst == ShaderIrInst.Texs ||
- op.Inst == ShaderIrInst.Tld4 ||
- op.Inst == ShaderIrInst.Txlf)
- {
- int handle = ((ShaderIrOperImm)op.OperandC).Value;
-
- int index = handle - TexStartIndex;
-
- string name = _stagePrefix + TextureName + index;
-
- GalTextureTarget textureTarget;
-
- TextureInstructionSuffix textureInstructionSuffix;
-
- // TODO: Non 2D texture type for TEXQ?
- if (op.Inst == ShaderIrInst.Texq)
- {
- textureTarget = GalTextureTarget.TwoD;
- textureInstructionSuffix = TextureInstructionSuffix.None;
- }
- else
- {
- ShaderIrMetaTex meta = ((ShaderIrMetaTex)op.MetaData);
-
- textureTarget = meta.TextureTarget;
- textureInstructionSuffix = meta.TextureInstructionSuffix;
- }
-
- m_Textures.TryAdd(handle, new ShaderDeclInfo(name, handle, false, 0, 1, textureTarget, textureInstructionSuffix));
- }
- else if (op.Inst == ShaderIrInst.Texb)
- {
- ShaderIrNode handleSrc = null;
-
- int index = Array.IndexOf(nodes, parent) - 1;
-
- for (; index >= 0; index--)
- {
- ShaderIrNode curr = nodes[index];
-
- if (curr is ShaderIrAsg asg && asg.Dst is ShaderIrOperGpr gpr)
- {
- if (gpr.Index == ((ShaderIrOperGpr)op.OperandC).Index)
- {
- handleSrc = asg.Src;
-
- break;
- }
- }
- }
-
- if (handleSrc != null && handleSrc is ShaderIrOperCbuf cbuf)
- {
- ShaderIrMetaTex meta = ((ShaderIrMetaTex)op.MetaData);
- string name = _stagePrefix + TextureName + "_cb" + cbuf.Index + "_" + cbuf.Pos;
-
- m_CbTextures.Add(op, new ShaderDeclInfo(name, cbuf.Pos, true, cbuf.Index, 1, meta.TextureTarget, meta.TextureInstructionSuffix));
- }
- else
- {
- throw new NotImplementedException("Shader TEX.B instruction is not fully supported!");
- }
- }
- break;
- }
-
- case ShaderIrOperCbuf cbuf:
- {
- if (!m_Uniforms.ContainsKey(cbuf.Index))
- {
- string name = _stagePrefix + UniformName + cbuf.Index;
-
- ShaderDeclInfo declInfo = new ShaderDeclInfo(name, cbuf.Pos, true, cbuf.Index);
-
- m_Uniforms.Add(cbuf.Index, declInfo);
- }
- break;
- }
-
- case ShaderIrOperAbuf abuf:
- {
- //This is a built-in variable.
- if (abuf.Offs == LayerAttr ||
- abuf.Offs == PointSizeAttr ||
- abuf.Offs == PointCoordAttrX ||
- abuf.Offs == PointCoordAttrY ||
- abuf.Offs == VertexIdAttr ||
- abuf.Offs == InstanceIdAttr ||
- abuf.Offs == FaceAttr)
- {
- break;
- }
-
- int index = abuf.Offs >> 4;
- int elem = (abuf.Offs >> 2) & 3;
-
- int glslIndex = index - AttrStartIndex;
-
- if (glslIndex < 0)
- {
- return;
- }
-
- ShaderDeclInfo declInfo;
-
- if (parent is ShaderIrAsg asg && asg.Dst == node)
- {
- if (!m_OutAttributes.TryGetValue(index, out declInfo))
- {
- declInfo = new ShaderDeclInfo(OutAttrName + glslIndex, glslIndex);
-
- m_OutAttributes.Add(index, declInfo);
- }
- }
- else
- {
- if (!m_InAttributes.TryGetValue(index, out declInfo))
- {
- declInfo = new ShaderDeclInfo(InAttrName + glslIndex, glslIndex);
-
- m_InAttributes.Add(index, declInfo);
- }
- }
-
- declInfo.Enlarge(elem + 1);
-
- if (!m_Attributes.ContainsKey(index))
- {
- declInfo = new ShaderDeclInfo(AttrName + glslIndex, glslIndex, false, 0, 4);
-
- m_Attributes.Add(index, declInfo);
- }
-
- Traverse(nodes, abuf, abuf.Vertex);
-
- break;
- }
-
- case ShaderIrOperGpr gpr:
- {
- if (!gpr.IsConst)
- {
- string name = GetGprName(gpr.Index);
-
- if (gpr.RegisterSize == ShaderRegisterSize.Single)
- {
- m_Gprs.TryAdd(gpr.Index, new ShaderDeclInfo(name, gpr.Index));
- }
- else if (gpr.RegisterSize == ShaderRegisterSize.Half)
- {
- name += "_h" + gpr.HalfPart;
-
- m_GprsHalf.TryAdd((gpr.Index << 1) | gpr.HalfPart, new ShaderDeclInfo(name, gpr.Index));
- }
- else /* if (Gpr.RegisterSize == ShaderRegisterSize.Double) */
- {
- throw new NotImplementedException("Double types are not supported.");
- }
- }
- break;
- }
-
- case ShaderIrOperPred pred:
- {
- if (!pred.IsConst && !HasName(m_Preds, pred.Index))
- {
- string name = PredName + pred.Index;
-
- m_Preds.TryAdd(pred.Index, new ShaderDeclInfo(name, pred.Index));
- }
- break;
- }
- }
- }
-
- private bool HasName(Dictionary<int, ShaderDeclInfo> decls, int index)
- {
- //This is used to check if the dictionary already contains
- //a entry for a vector at a given index position.
- //Used to enable turning gprs into vectors.
- int vecIndex = index & ~3;
-
- if (decls.TryGetValue(vecIndex, out ShaderDeclInfo declInfo))
- {
- if (declInfo.Size > 1 && index < vecIndex + declInfo.Size)
- {
- return true;
- }
- }
-
- return decls.ContainsKey(index);
- }
- }
-}
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
deleted file mode 100644
index 228a9018..00000000
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
+++ /dev/null
@@ -1,1679 +0,0 @@
-using OpenTK.Graphics.OpenGL;
-using Ryujinx.Graphics.Texture;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- public class GlslDecompiler
- {
- private delegate string GetInstExpr(ShaderIrOp op);
-
- private Dictionary<ShaderIrInst, GetInstExpr> _instsExpr;
-
- private enum OperType
- {
- Bool,
- F32,
- I32
- }
-
- private const string IdentationStr = " ";
-
- private const int MaxVertexInput = 3;
-
- private GlslDecl _decl;
-
- private ShaderHeader _header, _headerB;
-
- private ShaderIrBlock[] _blocks, _blocksB;
-
- private StringBuilder _sb;
-
- public int MaxUboSize { get; }
-
- private bool _isNvidiaDriver;
-
- public GlslDecompiler(int maxUboSize, bool isNvidiaDriver)
- {
- _instsExpr = new Dictionary<ShaderIrInst, GetInstExpr>()
- {
- { ShaderIrInst.Abs, GetAbsExpr },
- { ShaderIrInst.Add, GetAddExpr },
- { ShaderIrInst.And, GetAndExpr },
- { ShaderIrInst.Asr, GetAsrExpr },
- { ShaderIrInst.Band, GetBandExpr },
- { ShaderIrInst.Bnot, GetBnotExpr },
- { ShaderIrInst.Bor, GetBorExpr },
- { ShaderIrInst.Bxor, GetBxorExpr },
- { ShaderIrInst.Ceil, GetCeilExpr },
- { ShaderIrInst.Ceq, GetCeqExpr },
- { ShaderIrInst.Cge, GetCgeExpr },
- { ShaderIrInst.Cgt, GetCgtExpr },
- { ShaderIrInst.Clamps, GetClampsExpr },
- { ShaderIrInst.Clampu, GetClampuExpr },
- { ShaderIrInst.Cle, GetCleExpr },
- { ShaderIrInst.Clt, GetCltExpr },
- { ShaderIrInst.Cne, GetCneExpr },
- { ShaderIrInst.Cut, GetCutExpr },
- { ShaderIrInst.Exit, GetExitExpr },
- { ShaderIrInst.Fabs, GetAbsExpr },
- { ShaderIrInst.Fadd, GetAddExpr },
- { ShaderIrInst.Fceq, GetCeqExpr },
- { ShaderIrInst.Fcequ, GetCequExpr },
- { ShaderIrInst.Fcge, GetCgeExpr },
- { ShaderIrInst.Fcgeu, GetCgeuExpr },
- { ShaderIrInst.Fcgt, GetCgtExpr },
- { ShaderIrInst.Fcgtu, GetCgtuExpr },
- { ShaderIrInst.Fclamp, GetFclampExpr },
- { ShaderIrInst.Fcle, GetCleExpr },
- { ShaderIrInst.Fcleu, GetCleuExpr },
- { ShaderIrInst.Fclt, GetCltExpr },
- { ShaderIrInst.Fcltu, GetCltuExpr },
- { ShaderIrInst.Fcnan, GetCnanExpr },
- { ShaderIrInst.Fcne, GetCneExpr },
- { ShaderIrInst.Fcneu, GetCneuExpr },
- { ShaderIrInst.Fcnum, GetCnumExpr },
- { ShaderIrInst.Fcos, GetFcosExpr },
- { ShaderIrInst.Fex2, GetFex2Expr },
- { ShaderIrInst.Ffma, GetFfmaExpr },
- { ShaderIrInst.Flg2, GetFlg2Expr },
- { ShaderIrInst.Floor, GetFloorExpr },
- { ShaderIrInst.Fmax, GetMaxExpr },
- { ShaderIrInst.Fmin, GetMinExpr },
- { ShaderIrInst.Fmul, GetMulExpr },
- { ShaderIrInst.Fneg, GetNegExpr },
- { ShaderIrInst.Frcp, GetFrcpExpr },
- { ShaderIrInst.Frsq, GetFrsqExpr },
- { ShaderIrInst.Fsin, GetFsinExpr },
- { ShaderIrInst.Fsqrt, GetFsqrtExpr },
- { ShaderIrInst.Ftos, GetFtosExpr },
- { ShaderIrInst.Ftou, GetFtouExpr },
- { ShaderIrInst.Ipa, GetIpaExpr },
- { ShaderIrInst.Kil, GetKilExpr },
- { ShaderIrInst.Lsl, GetLslExpr },
- { ShaderIrInst.Lsr, GetLsrExpr },
- { ShaderIrInst.Max, GetMaxExpr },
- { ShaderIrInst.Min, GetMinExpr },
- { ShaderIrInst.Mul, GetMulExpr },
- { ShaderIrInst.Neg, GetNegExpr },
- { ShaderIrInst.Not, GetNotExpr },
- { ShaderIrInst.Or, GetOrExpr },
- { ShaderIrInst.Stof, GetStofExpr },
- { ShaderIrInst.Sub, GetSubExpr },
- { ShaderIrInst.Texb, GetTexbExpr },
- { ShaderIrInst.Texq, GetTexqExpr },
- { ShaderIrInst.Texs, GetTexsExpr },
- { ShaderIrInst.Tld4, GetTld4Expr },
- { ShaderIrInst.Trunc, GetTruncExpr },
- { ShaderIrInst.Txlf, GetTxlfExpr },
- { ShaderIrInst.Utof, GetUtofExpr },
- { ShaderIrInst.Xor, GetXorExpr }
- };
-
- MaxUboSize = maxUboSize / 16;
- _isNvidiaDriver = isNvidiaDriver;
- }
-
- public GlslProgram Decompile(
- IGalMemory memory,
- long vpAPosition,
- long vpBPosition,
- GalShaderType shaderType)
- {
- _header = new ShaderHeader(memory, vpAPosition);
- _headerB = new ShaderHeader(memory, vpBPosition);
-
- _blocks = ShaderDecoder.Decode(memory, vpAPosition);
- _blocksB = ShaderDecoder.Decode(memory, vpBPosition);
-
- GlslDecl declVpA = new GlslDecl(_blocks, shaderType, _header);
- GlslDecl declVpB = new GlslDecl(_blocksB, shaderType, _headerB);
-
- _decl = GlslDecl.Merge(declVpA, declVpB);
-
- return Decompile();
- }
-
- public GlslProgram Decompile(IGalMemory memory, long position, GalShaderType shaderType)
- {
- _header = new ShaderHeader(memory, position);
- _headerB = null;
-
- _blocks = ShaderDecoder.Decode(memory, position);
- _blocksB = null;
-
- _decl = new GlslDecl(_blocks, shaderType, _header);
-
- return Decompile();
- }
-
- private GlslProgram Decompile()
- {
- _sb = new StringBuilder();
-
- _sb.AppendLine("#version 410 core");
-
- PrintDeclHeader();
- PrintDeclTextures();
- PrintDeclUniforms();
- PrintDeclAttributes();
- PrintDeclInAttributes();
- PrintDeclOutAttributes();
- PrintDeclGprs();
- PrintDeclPreds();
- PrintDeclSsy();
-
- if (_blocksB != null)
- {
- PrintBlockScope(_blocks, GlslDecl.BasicBlockAName);
-
- _sb.AppendLine();
-
- PrintBlockScope(_blocksB, GlslDecl.BasicBlockBName);
- }
- else
- {
- PrintBlockScope(_blocks, GlslDecl.BasicBlockName);
- }
-
- _sb.AppendLine();
-
- PrintMain();
-
- string glslCode = _sb.ToString();
-
- List<ShaderDeclInfo> textureInfo = new List<ShaderDeclInfo>();
-
- textureInfo.AddRange(_decl.Textures.Values);
- textureInfo.AddRange(IterateCbTextures());
-
- return new GlslProgram(glslCode, textureInfo, _decl.Uniforms.Values);
- }
-
- private void PrintDeclHeader()
- {
- if (_decl.ShaderType == GalShaderType.Geometry)
- {
- int maxVertices = _header.MaxOutputVertexCount;
-
- string outputTopology;
-
- switch (_header.OutputTopology)
- {
- case ShaderHeader.PointList: outputTopology = "points"; break;
- case ShaderHeader.LineStrip: outputTopology = "line_strip"; break;
- case ShaderHeader.TriangleStrip: outputTopology = "triangle_strip"; break;
-
- default: throw new InvalidOperationException();
- }
-
- _sb.AppendLine("#extension GL_ARB_enhanced_layouts : require");
-
- _sb.AppendLine();
-
- _sb.AppendLine("// Stubbed. Maxwell geometry shaders don't inform input geometry type");
-
- _sb.AppendLine("layout(triangles) in;" + Environment.NewLine);
-
- _sb.AppendLine($"layout({outputTopology}, max_vertices = {maxVertices}) out;");
-
- _sb.AppendLine();
- }
- }
-
- private string GetSamplerType(TextureTarget textureTarget, bool hasShadow)
- {
- string result;
-
- switch (textureTarget)
- {
- case TextureTarget.Texture1D:
- result = "sampler1D";
- break;
- case TextureTarget.Texture2D:
- result = "sampler2D";
- break;
- case TextureTarget.Texture3D:
- result = "sampler3D";
- break;
- case TextureTarget.TextureCubeMap:
- result = "samplerCube";
- break;
- case TextureTarget.TextureRectangle:
- result = "sampler2DRect";
- break;
- case TextureTarget.Texture1DArray:
- result = "sampler1DArray";
- break;
- case TextureTarget.Texture2DArray:
- result = "sampler2DArray";
- break;
- case TextureTarget.TextureCubeMapArray:
- result = "samplerCubeArray";
- break;
- case TextureTarget.TextureBuffer:
- result = "samplerBuffer";
- break;
- case TextureTarget.Texture2DMultisample:
- result = "sampler2DMS";
- break;
- case TextureTarget.Texture2DMultisampleArray:
- result = "sampler2DMSArray";
- break;
- default:
- throw new NotSupportedException();
- }
-
- if (hasShadow)
- result += "Shadow";
-
- return result;
- }
-
- private void PrintDeclTextures()
- {
- foreach (ShaderDeclInfo declInfo in IterateCbTextures())
- {
- TextureTarget target = ImageUtils.GetTextureTarget(declInfo.TextureTarget);
- _sb.AppendLine($"// {declInfo.TextureSuffix}");
- _sb.AppendLine("uniform " + GetSamplerType(target, (declInfo.TextureSuffix & TextureInstructionSuffix.Dc) != 0) + " " + declInfo.Name + ";");
- }
-
- foreach (ShaderDeclInfo declInfo in _decl.Textures.Values.OrderBy(DeclKeySelector))
- {
- TextureTarget target = ImageUtils.GetTextureTarget(declInfo.TextureTarget);
- _sb.AppendLine($"// {declInfo.TextureSuffix}");
- _sb.AppendLine("uniform " + GetSamplerType(target, (declInfo.TextureSuffix & TextureInstructionSuffix.Dc) != 0) + " " + declInfo.Name + ";");
- }
- }
-
- private IEnumerable<ShaderDeclInfo> IterateCbTextures()
- {
- HashSet<string> names = new HashSet<string>();
-
- foreach (ShaderDeclInfo declInfo in _decl.CbTextures.Values.OrderBy(DeclKeySelector))
- {
- if (names.Add(declInfo.Name))
- {
- yield return declInfo;
- }
- }
- }
-
- private void PrintDeclUniforms()
- {
- if (_decl.ShaderType == GalShaderType.Vertex)
- {
- //Memory layout here is [flip_x, flip_y, instance, unused]
- //It's using 4 bytes, not 8
-
- _sb.AppendLine("layout (std140) uniform " + GlslDecl.ExtraUniformBlockName + " {");
-
- _sb.AppendLine(IdentationStr + "vec2 " + GlslDecl.FlipUniformName + ";");
-
- _sb.AppendLine(IdentationStr + "int " + GlslDecl.InstanceUniformName + ";");
-
- _sb.AppendLine("};");
- _sb.AppendLine();
- }
-
- foreach (ShaderDeclInfo declInfo in _decl.Uniforms.Values.OrderBy(DeclKeySelector))
- {
- _sb.AppendLine($"layout (std140) uniform {declInfo.Name} {{");
-
- _sb.AppendLine($"{IdentationStr}vec4 {declInfo.Name}_data[{MaxUboSize}];");
-
- _sb.AppendLine("};");
- }
-
- if (_decl.Uniforms.Count > 0)
- {
- _sb.AppendLine();
- }
- }
-
- private void PrintDeclAttributes()
- {
- string geometryArray = (_decl.ShaderType == GalShaderType.Geometry) ? "[" + MaxVertexInput + "]" : "";
-
- PrintDecls(_decl.Attributes, suffix: geometryArray);
- }
-
- private void PrintDeclInAttributes()
- {
- if (_decl.ShaderType == GalShaderType.Fragment)
- {
- _sb.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") in vec4 " + GlslDecl.PositionOutAttrName + ";");
- }
-
- if (_decl.ShaderType == GalShaderType.Geometry)
- {
- if (_decl.InAttributes.Count > 0)
- {
- _sb.AppendLine("in Vertex {");
-
- foreach (ShaderDeclInfo declInfo in _decl.InAttributes.Values.OrderBy(DeclKeySelector))
- {
- if (declInfo.Index >= 0)
- {
- _sb.AppendLine(IdentationStr + "layout (location = " + declInfo.Index + ") vec4 " + declInfo.Name + "; ");
- }
- }
-
- _sb.AppendLine("} block_in[];" + Environment.NewLine);
- }
- }
- else
- {
- PrintDeclAttributes(_decl.InAttributes.Values, "in");
- }
- }
-
- private void PrintDeclOutAttributes()
- {
- if (_decl.ShaderType == GalShaderType.Fragment)
- {
- int count = 0;
-
- for (int attachment = 0; attachment < 8; attachment++)
- {
- if (_header.OmapTargets[attachment].Enabled)
- {
- _sb.AppendLine("layout (location = " + attachment + ") out vec4 " + GlslDecl.FragmentOutputName + attachment + ";");
-
- count++;
- }
- }
-
- if (count > 0)
- {
- _sb.AppendLine();
- }
- }
- else
- {
- _sb.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") out vec4 " + GlslDecl.PositionOutAttrName + ";");
- _sb.AppendLine();
- }
-
- PrintDeclAttributes(_decl.OutAttributes.Values, "out");
- }
-
- private void PrintDeclAttributes(IEnumerable<ShaderDeclInfo> decls, string inOut)
- {
- int count = 0;
-
- foreach (ShaderDeclInfo declInfo in decls.OrderBy(DeclKeySelector))
- {
- if (declInfo.Index >= 0)
- {
- _sb.AppendLine("layout (location = " + declInfo.Index + ") " + inOut + " vec4 " + declInfo.Name + ";");
-
- count++;
- }
- }
-
- if (count > 0)
- {
- _sb.AppendLine();
- }
- }
-
- private void PrintDeclGprs()
- {
- PrintDecls(_decl.Gprs);
- PrintDecls(_decl.GprsHalf);
- }
-
- private void PrintDeclPreds()
- {
- PrintDecls(_decl.Preds, "bool");
- }
-
- private void PrintDeclSsy()
- {
- _sb.AppendLine("uint " + GlslDecl.SsyCursorName + " = 0;");
-
- _sb.AppendLine("uint " + GlslDecl.SsyStackName + "[" + GlslDecl.SsyStackSize + "];" + Environment.NewLine);
- }
-
- private void PrintDecls(IReadOnlyDictionary<int, ShaderDeclInfo> dict, string customType = null, string suffix = "")
- {
- foreach (ShaderDeclInfo declInfo in dict.Values.OrderBy(DeclKeySelector))
- {
- string name;
-
- if (customType != null)
- {
- name = customType + " " + declInfo.Name + suffix + ";";
- }
- else if (declInfo.Name.Contains(GlslDecl.FragmentOutputName))
- {
- name = "layout (location = " + declInfo.Index / 4 + ") out vec4 " + declInfo.Name + suffix + ";";
- }
- else
- {
- name = GetDecl(declInfo) + suffix + ";";
- }
-
- _sb.AppendLine(name);
- }
-
- if (dict.Count > 0)
- {
- _sb.AppendLine();
- }
- }
-
- private int DeclKeySelector(ShaderDeclInfo declInfo)
- {
- return declInfo.Cbuf << 24 | declInfo.Index;
- }
-
- private string GetDecl(ShaderDeclInfo declInfo)
- {
- if (declInfo.Size == 4)
- {
- return "vec4 " + declInfo.Name;
- }
- else
- {
- return "float " + declInfo.Name;
- }
- }
-
- private void PrintMain()
- {
- _sb.AppendLine("void main() {");
-
- foreach (KeyValuePair<int, ShaderDeclInfo> kv in _decl.InAttributes)
- {
- if (!_decl.Attributes.TryGetValue(kv.Key, out ShaderDeclInfo attr))
- {
- continue;
- }
-
- ShaderDeclInfo declInfo = kv.Value;
-
- if (_decl.ShaderType == GalShaderType.Geometry)
- {
- for (int vertex = 0; vertex < MaxVertexInput; vertex++)
- {
- string dst = attr.Name + "[" + vertex + "]";
-
- string src = "block_in[" + vertex + "]." + declInfo.Name;
-
- _sb.AppendLine(IdentationStr + dst + " = " + src + ";");
- }
- }
- else
- {
- _sb.AppendLine(IdentationStr + attr.Name + " = " + declInfo.Name + ";");
- }
- }
-
- _sb.AppendLine(IdentationStr + "uint pc;");
-
- if (_blocksB != null)
- {
- PrintProgram(_blocks, GlslDecl.BasicBlockAName);
- PrintProgram(_blocksB, GlslDecl.BasicBlockBName);
- }
- else
- {
- PrintProgram(_blocks, GlslDecl.BasicBlockName);
- }
-
- if (_decl.ShaderType != GalShaderType.Geometry)
- {
- PrintAttrToOutput();
- }
-
- if (_decl.ShaderType == GalShaderType.Fragment)
- {
- if (_header.OmapDepth)
- {
- _sb.AppendLine(IdentationStr + "gl_FragDepth = " + GlslDecl.GetGprName(_header.DepthRegister) + ";");
- }
-
- int gprIndex = 0;
-
- for (int attachment = 0; attachment < 8; attachment++)
- {
- string output = GlslDecl.FragmentOutputName + attachment;
-
- OmapTarget target = _header.OmapTargets[attachment];
-
- for (int component = 0; component < 4; component++)
- {
- if (target.ComponentEnabled(component))
- {
- _sb.AppendLine(IdentationStr + output + "[" + component + "] = " + GlslDecl.GetGprName(gprIndex) + ";");
-
- gprIndex++;
- }
- }
- }
- }
-
- _sb.AppendLine("}");
- }
-
- private void PrintProgram(ShaderIrBlock[] blocks, string name)
- {
- const string ident1 = IdentationStr;
- const string ident2 = ident1 + IdentationStr;
- const string ident3 = ident2 + IdentationStr;
- const string ident4 = ident3 + IdentationStr;
-
- _sb.AppendLine(ident1 + "pc = " + GetBlockPosition(blocks[0]) + ";");
- _sb.AppendLine(ident1 + "do {");
- _sb.AppendLine(ident2 + "switch (pc) {");
-
- foreach (ShaderIrBlock block in blocks)
- {
- string functionName = block.Position.ToString("x8");
-
- _sb.AppendLine(ident3 + "case 0x" + functionName + ": pc = " + name + "_" + functionName + "(); break;");
- }
-
- _sb.AppendLine(ident3 + "default:");
- _sb.AppendLine(ident4 + "pc = 0;");
- _sb.AppendLine(ident4 + "break;");
-
- _sb.AppendLine(ident2 + "}");
- _sb.AppendLine(ident1 + "} while (pc != 0);");
- }
-
- private void PrintAttrToOutput(string identation = IdentationStr)
- {
- foreach (KeyValuePair<int, ShaderDeclInfo> kv in _decl.OutAttributes)
- {
- if (!_decl.Attributes.TryGetValue(kv.Key, out ShaderDeclInfo attr))
- {
- continue;
- }
-
- ShaderDeclInfo declInfo = kv.Value;
-
- string name = attr.Name;
-
- if (_decl.ShaderType == GalShaderType.Geometry)
- {
- name += "[0]";
- }
-
- _sb.AppendLine(identation + declInfo.Name + " = " + name + ";");
- }
-
- if (_decl.ShaderType == GalShaderType.Vertex)
- {
- _sb.AppendLine(identation + "gl_Position.xy *= " + GlslDecl.FlipUniformName + ";");
- }
-
- if (_decl.ShaderType != GalShaderType.Fragment)
- {
- _sb.AppendLine(identation + GlslDecl.PositionOutAttrName + " = gl_Position;");
- _sb.AppendLine(identation + GlslDecl.PositionOutAttrName + ".w = 1;");
- }
- }
-
- private void PrintBlockScope(ShaderIrBlock[] blocks, string name)
- {
- foreach (ShaderIrBlock block in blocks)
- {
- _sb.AppendLine("uint " + name + "_" + block.Position.ToString("x8") + "() {");
-
- PrintNodes(block, block.GetNodes());
-
- _sb.AppendLine("}" + Environment.NewLine);
- }
- }
-
- private void PrintNodes(ShaderIrBlock block, ShaderIrNode[] nodes)
- {
- foreach (ShaderIrNode node in nodes)
- {
- PrintNode(block, node, IdentationStr);
- }
-
- if (nodes.Length == 0)
- {
- _sb.AppendLine(IdentationStr + "return 0u;");
-
- return;
- }
-
- ShaderIrNode last = nodes[nodes.Length - 1];
-
- bool unconditionalFlowChange = false;
-
- if (last is ShaderIrOp op)
- {
- switch (op.Inst)
- {
- case ShaderIrInst.Bra:
- case ShaderIrInst.Exit:
- case ShaderIrInst.Sync:
- unconditionalFlowChange = true;
- break;
- }
- }
-
- if (!unconditionalFlowChange)
- {
- if (block.Next != null)
- {
- _sb.AppendLine(IdentationStr + "return " + GetBlockPosition(block.Next) + ";");
- }
- else
- {
- _sb.AppendLine(IdentationStr + "return 0u;");
- }
- }
- }
-
- private void PrintNode(ShaderIrBlock block, ShaderIrNode node, string identation)
- {
- if (node is ShaderIrCond cond)
- {
- string ifExpr = GetSrcExpr(cond.Pred, true);
-
- if (cond.Not)
- {
- ifExpr = "!(" + ifExpr + ")";
- }
-
- _sb.AppendLine(identation + "if (" + ifExpr + ") {");
-
- PrintNode(block, cond.Child, identation + IdentationStr);
-
- _sb.AppendLine(identation + "}");
- }
- else if (node is ShaderIrAsg asg)
- {
- if (IsValidOutOper(asg.Dst))
- {
- string expr = GetSrcExpr(asg.Src, true);
-
- expr = GetExprWithCast(asg.Dst, asg.Src, expr);
-
- _sb.AppendLine(identation + GetDstOperName(asg.Dst) + " = " + expr + ";");
- }
- }
- else if (node is ShaderIrOp op)
- {
- switch (op.Inst)
- {
- case ShaderIrInst.Bra:
- {
- _sb.AppendLine(identation + "return " + GetBlockPosition(block.Branch) + ";");
-
- break;
- }
-
- case ShaderIrInst.Emit:
- {
- PrintAttrToOutput(identation);
-
- _sb.AppendLine(identation + "EmitVertex();");
-
- break;
- }
-
- case ShaderIrInst.Ssy:
- {
- string stackIndex = GlslDecl.SsyStackName + "[" + GlslDecl.SsyCursorName + "]";
-
- int targetPosition = (op.OperandA as ShaderIrOperImm).Value;
-
- string target = "0x" + targetPosition.ToString("x8") + "u";
-
- _sb.AppendLine(identation + stackIndex + " = " + target + ";");
-
- _sb.AppendLine(identation + GlslDecl.SsyCursorName + "++;");
-
- break;
- }
-
- case ShaderIrInst.Sync:
- {
- _sb.AppendLine(identation + GlslDecl.SsyCursorName + "--;");
-
- string target = GlslDecl.SsyStackName + "[" + GlslDecl.SsyCursorName + "]";
-
- _sb.AppendLine(identation + "return " + target + ";");
-
- break;
- }
-
- default:
- _sb.AppendLine(identation + GetSrcExpr(op, true) + ";");
-
- break;
- }
- }
- else if (node is ShaderIrCmnt cmnt)
- {
- _sb.AppendLine(identation + "// " + cmnt.Comment);
- }
- else
- {
- throw new InvalidOperationException();
- }
- }
-
- private bool IsValidOutOper(ShaderIrNode node)
- {
- if (node is ShaderIrOperGpr gpr && gpr.IsConst)
- {
- return false;
- }
- else if (node is ShaderIrOperPred pred && pred.IsConst)
- {
- return false;
- }
-
- return true;
- }
-
- private string GetDstOperName(ShaderIrNode node)
- {
- if (node is ShaderIrOperAbuf abuf)
- {
- return GetOutAbufName(abuf);
- }
- else if (node is ShaderIrOperGpr gpr)
- {
- return GetName(gpr);
- }
- else if (node is ShaderIrOperPred pred)
- {
- return GetName(pred);
- }
-
- throw new ArgumentException(nameof(node));
- }
-
- private string GetSrcExpr(ShaderIrNode node, bool entry = false)
- {
- switch (node)
- {
- case ShaderIrOperAbuf abuf: return GetName (abuf);
- case ShaderIrOperCbuf cbuf: return GetName (cbuf);
- case ShaderIrOperGpr gpr: return GetName (gpr);
- case ShaderIrOperImm imm: return GetValue(imm);
- case ShaderIrOperImmf immf: return GetValue(immf);
- case ShaderIrOperPred pred: return GetName (pred);
-
- case ShaderIrOp op:
- string expr;
-
- if (_instsExpr.TryGetValue(op.Inst, out GetInstExpr getExpr))
- {
- expr = getExpr(op);
- }
- else
- {
- throw new NotImplementedException(op.Inst.ToString());
- }
-
- if (!entry && NeedsParentheses(op))
- {
- expr = "(" + expr + ")";
- }
-
- return expr;
-
- default: throw new ArgumentException(nameof(node));
- }
- }
-
- private static bool NeedsParentheses(ShaderIrOp op)
- {
- switch (op.Inst)
- {
- case ShaderIrInst.Ipa:
- case ShaderIrInst.Texq:
- case ShaderIrInst.Texs:
- case ShaderIrInst.Tld4:
- case ShaderIrInst.Txlf:
- return false;
- }
-
- return true;
- }
-
- private string GetName(ShaderIrOperCbuf cbuf)
- {
- if (!_decl.Uniforms.TryGetValue(cbuf.Index, out ShaderDeclInfo declInfo))
- {
- throw new InvalidOperationException();
- }
-
- if (cbuf.Offs != null)
- {
- string offset = "floatBitsToInt(" + GetSrcExpr(cbuf.Offs) + ")";
-
- string index = "(" + cbuf.Pos * 4 + " + " + offset + ")";
-
- return $"{declInfo.Name}_data[{index} / 16][({index} / 4) % 4]";
- }
- else
- {
- return $"{declInfo.Name}_data[{cbuf.Pos / 4}][{cbuf.Pos % 4}]";
- }
- }
-
- private string GetOutAbufName(ShaderIrOperAbuf abuf)
- {
- if (_decl.ShaderType == GalShaderType.Geometry)
- {
- switch (abuf.Offs)
- {
- case GlslDecl.LayerAttr: return "gl_Layer";
- }
- }
-
- return GetAttrTempName(abuf);
- }
-
- private string GetName(ShaderIrOperAbuf abuf)
- {
- //Handle special scalar read-only attributes here.
- if (_decl.ShaderType == GalShaderType.Vertex)
- {
- switch (abuf.Offs)
- {
- case GlslDecl.VertexIdAttr: return "gl_VertexID";
- case GlslDecl.InstanceIdAttr: return GlslDecl.InstanceUniformName;
- }
- }
- else if (_decl.ShaderType == GalShaderType.TessEvaluation)
- {
- switch (abuf.Offs)
- {
- case GlslDecl.TessCoordAttrX: return "gl_TessCoord.x";
- case GlslDecl.TessCoordAttrY: return "gl_TessCoord.y";
- case GlslDecl.TessCoordAttrZ: return "gl_TessCoord.z";
- }
- }
- else if (_decl.ShaderType == GalShaderType.Fragment)
- {
- switch (abuf.Offs)
- {
- case GlslDecl.PointCoordAttrX: return "gl_PointCoord.x";
- case GlslDecl.PointCoordAttrY: return "gl_PointCoord.y";
- case GlslDecl.FaceAttr: return "(gl_FrontFacing ? -1 : 0)";
- }
- }
-
- return GetAttrTempName(abuf);
- }
-
- private string GetAttrTempName(ShaderIrOperAbuf abuf)
- {
- int index = abuf.Offs >> 4;
- int elem = (abuf.Offs >> 2) & 3;
-
- string swizzle = "." + GetAttrSwizzle(elem);
-
- if (!_decl.Attributes.TryGetValue(index, out ShaderDeclInfo declInfo))
- {
- //Handle special vec4 attributes here
- //(for example, index 7 is always gl_Position).
- if (index == GlslDecl.GlPositionVec4Index)
- {
- string name =
- _decl.ShaderType != GalShaderType.Vertex &&
- _decl.ShaderType != GalShaderType.Geometry ? GlslDecl.PositionOutAttrName : "gl_Position";
-
- return name + swizzle;
- }
- else if (abuf.Offs == GlslDecl.PointSizeAttr)
- {
- return "gl_PointSize";
- }
- }
-
- if (declInfo.Index >= 32)
- {
- throw new InvalidOperationException($"Shader attribute offset {abuf.Offs} is invalid.");
- }
-
- if (_decl.ShaderType == GalShaderType.Geometry)
- {
- string vertex = "floatBitsToInt(" + GetSrcExpr(abuf.Vertex) + ")";
-
- return declInfo.Name + "[" + vertex + "]" + swizzle;
- }
- else
- {
- return declInfo.Name + swizzle;
- }
- }
-
- private string GetName(ShaderIrOperGpr gpr)
- {
- if (gpr.IsConst)
- {
- return "0";
- }
-
- if (gpr.RegisterSize == ShaderRegisterSize.Single)
- {
- return GetNameWithSwizzle(_decl.Gprs, gpr.Index);
- }
- else if (gpr.RegisterSize == ShaderRegisterSize.Half)
- {
- return GetNameWithSwizzle(_decl.GprsHalf, (gpr.Index << 1) | gpr.HalfPart);
- }
- else /* if (Gpr.RegisterSize == ShaderRegisterSize.Double) */
- {
- throw new NotImplementedException("Double types are not supported.");
- }
- }
-
- private string GetValue(ShaderIrOperImm imm)
- {
- //Only use hex is the value is too big and would likely be hard to read as int.
- if (imm.Value > 0xfff ||
- imm.Value < -0xfff)
- {
- return "0x" + imm.Value.ToString("x8", CultureInfo.InvariantCulture);
- }
- else
- {
- return GetIntConst(imm.Value);
- }
- }
-
- private string GetValue(ShaderIrOperImmf immf)
- {
- return GetFloatConst(immf.Value);
- }
-
- private string GetName(ShaderIrOperPred pred)
- {
- return pred.IsConst ? "true" : GetNameWithSwizzle(_decl.Preds, pred.Index);
- }
-
- private string GetNameWithSwizzle(IReadOnlyDictionary<int, ShaderDeclInfo> dict, int index)
- {
- int vecIndex = index & ~3;
-
- if (dict.TryGetValue(vecIndex, out ShaderDeclInfo declInfo))
- {
- if (declInfo.Size > 1 && index < vecIndex + declInfo.Size)
- {
- return declInfo.Name + "." + GetAttrSwizzle(index & 3);
- }
- }
-
- if (!dict.TryGetValue(index, out declInfo))
- {
- throw new InvalidOperationException();
- }
-
- return declInfo.Name;
- }
-
- private string GetAttrSwizzle(int elem)
- {
- return "xyzw".Substring(elem, 1);
- }
-
- private string GetAbsExpr(ShaderIrOp op) => GetUnaryCall(op, "abs");
-
- private string GetAddExpr(ShaderIrOp op) => GetBinaryExpr(op, "+");
-
- private string GetAndExpr(ShaderIrOp op) => GetBinaryExpr(op, "&");
-
- private string GetAsrExpr(ShaderIrOp op) => GetBinaryExpr(op, ">>");
-
- private string GetBandExpr(ShaderIrOp op) => GetBinaryExpr(op, "&&");
-
- private string GetBnotExpr(ShaderIrOp op) => GetUnaryExpr(op, "!");
-
- private string GetBorExpr(ShaderIrOp op) => GetBinaryExpr(op, "||");
-
- private string GetBxorExpr(ShaderIrOp op) => GetBinaryExpr(op, "^^");
-
- private string GetCeilExpr(ShaderIrOp op) => GetUnaryCall(op, "ceil");
-
- private string GetClampsExpr(ShaderIrOp op)
- {
- return "clamp(" + GetOperExpr(op, op.OperandA) + ", " +
- GetOperExpr(op, op.OperandB) + ", " +
- GetOperExpr(op, op.OperandC) + ")";
- }
-
- private string GetClampuExpr(ShaderIrOp op)
- {
- return "int(clamp(uint(" + GetOperExpr(op, op.OperandA) + "), " +
- "uint(" + GetOperExpr(op, op.OperandB) + "), " +
- "uint(" + GetOperExpr(op, op.OperandC) + ")))";
- }
-
- private string GetCeqExpr(ShaderIrOp op) => GetBinaryExpr(op, "==");
-
- private string GetCequExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "==");
-
- private string GetCgeExpr(ShaderIrOp op) => GetBinaryExpr(op, ">=");
-
- private string GetCgeuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, ">=");
-
- private string GetCgtExpr(ShaderIrOp op) => GetBinaryExpr(op, ">");
-
- private string GetCgtuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, ">");
-
- private string GetCleExpr(ShaderIrOp op) => GetBinaryExpr(op, "<=");
-
- private string GetCleuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "<=");
-
- private string GetCltExpr(ShaderIrOp op) => GetBinaryExpr(op, "<");
-
- private string GetCltuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "<");
-
- private string GetCnanExpr(ShaderIrOp op) => GetUnaryCall(op, "isnan");
-
- private string GetCneExpr(ShaderIrOp op) => GetBinaryExpr(op, "!=");
-
- private string GetCutExpr(ShaderIrOp op) => "EndPrimitive()";
-
- private string GetCneuExpr(ShaderIrOp op) => GetBinaryExprWithNaN(op, "!=");
-
- private string GetCnumExpr(ShaderIrOp op) => GetUnaryCall(op, "!isnan");
-
- private string GetExitExpr(ShaderIrOp op) => "return 0u";
-
- private string GetFcosExpr(ShaderIrOp op) => GetUnaryCall(op, "cos");
-
- private string GetFex2Expr(ShaderIrOp op) => GetUnaryCall(op, "exp2");
-
- private string GetFfmaExpr(ShaderIrOp op) => GetTernaryExpr(op, "*", "+");
-
- private string GetFclampExpr(ShaderIrOp op) => GetTernaryCall(op, "clamp");
-
- private string GetFlg2Expr(ShaderIrOp op) => GetUnaryCall(op, "log2");
-
- private string GetFloorExpr(ShaderIrOp op) => GetUnaryCall(op, "floor");
-
- private string GetFrcpExpr(ShaderIrOp op) => GetUnaryExpr(op, "1 / ");
-
- private string GetFrsqExpr(ShaderIrOp op) => GetUnaryCall(op, "inversesqrt");
-
- private string GetFsinExpr(ShaderIrOp op) => GetUnaryCall(op, "sin");
-
- private string GetFsqrtExpr(ShaderIrOp op) => GetUnaryCall(op, "sqrt");
-
- private string GetFtosExpr(ShaderIrOp op)
- {
- return "int(" + GetOperExpr(op, op.OperandA) + ")";
- }
-
- private string GetFtouExpr(ShaderIrOp op)
- {
- return "int(uint(" + GetOperExpr(op, op.OperandA) + "))";
- }
-
- private string GetIpaExpr(ShaderIrOp op)
- {
- ShaderIrMetaIpa meta = (ShaderIrMetaIpa)op.MetaData;
-
- ShaderIrOperAbuf abuf = (ShaderIrOperAbuf)op.OperandA;
-
- if (meta.Mode == ShaderIpaMode.Pass)
- {
- int index = abuf.Offs >> 4;
- int elem = (abuf.Offs >> 2) & 3;
-
- if (_decl.ShaderType == GalShaderType.Fragment && index == GlslDecl.GlPositionVec4Index)
- {
- switch (elem)
- {
- case 0: return "gl_FragCoord.x";
- case 1: return "gl_FragCoord.y";
- case 2: return "gl_FragCoord.z";
- case 3: return "1";
- }
- }
- }
-
- return GetSrcExpr(op.OperandA);
- }
-
- private string GetKilExpr(ShaderIrOp op) => "discard";
-
- private string GetLslExpr(ShaderIrOp op) => GetBinaryExpr(op, "<<");
- private string GetLsrExpr(ShaderIrOp op)
- {
- return "int(uint(" + GetOperExpr(op, op.OperandA) + ") >> " +
- GetOperExpr(op, op.OperandB) + ")";
- }
-
- private string GetMaxExpr(ShaderIrOp op) => GetBinaryCall(op, "max");
- private string GetMinExpr(ShaderIrOp op) => GetBinaryCall(op, "min");
-
- private string GetMulExpr(ShaderIrOp op) => GetBinaryExpr(op, "*");
-
- private string GetNegExpr(ShaderIrOp op) => GetUnaryExpr(op, "-");
-
- private string GetNotExpr(ShaderIrOp op) => GetUnaryExpr(op, "~");
-
- private string GetOrExpr(ShaderIrOp op) => GetBinaryExpr(op, "|");
-
- private string GetStofExpr(ShaderIrOp op)
- {
- return "float(" + GetOperExpr(op, op.OperandA) + ")";
- }
-
- private string GetSubExpr(ShaderIrOp op) => GetBinaryExpr(op, "-");
-
- private string GetTexbExpr(ShaderIrOp op)
- {
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- if (!_decl.CbTextures.TryGetValue(op, out ShaderDeclInfo declInfo))
- {
- throw new InvalidOperationException();
- }
-
- string coords = GetTexSamplerCoords(op);
-
- string ch = "rgba".Substring(meta.Elem, 1);
-
- return GetTextureOperation(op, declInfo.Name, coords, ch);
- }
-
- private string GetTexqExpr(ShaderIrOp op)
- {
- ShaderIrMetaTexq meta = (ShaderIrMetaTexq)op.MetaData;
-
- string ch = "xyzw".Substring(meta.Elem, 1);
-
- if (meta.Info == ShaderTexqInfo.Dimension)
- {
- string sampler = GetTexSamplerName(op);
-
- string lod = GetOperExpr(op, op.OperandA); //???
-
- return "textureSize(" + sampler + ", " + lod + ")." + ch;
- }
- else
- {
- throw new NotImplementedException(meta.Info.ToString());
- }
- }
-
- private string GetTexsExpr(ShaderIrOp op)
- {
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- string sampler = GetTexSamplerName(op);
-
- string coords = GetTexSamplerCoords(op);
-
- string ch = "rgba".Substring(meta.Elem, 1);
-
- return GetTextureOperation(op, sampler, coords, ch);
- }
-
- private string GetTld4Expr(ShaderIrOp op)
- {
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- string sampler = GetTexSamplerName(op);
-
- string coords = GetTexSamplerCoords(op);
-
- string ch = "rgba".Substring(meta.Elem, 1);
-
- return GetTextureGatherOperation(op, sampler, coords, ch);
- }
-
- // TODO: support AOFFI on non nvidia drivers
- private string GetTxlfExpr(ShaderIrOp op)
- {
- // TODO: Support all suffixes
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- TextureInstructionSuffix suffix = meta.TextureInstructionSuffix;
-
- string sampler = GetTexSamplerName(op);
-
- string coords = GetITexSamplerCoords(op);
-
- string ch = "rgba".Substring(meta.Elem, 1);
-
- string lod = "0";
-
- if (meta.LevelOfDetail != null)
- {
- lod = GetOperExpr(op, meta.LevelOfDetail);
- }
-
- if ((suffix & TextureInstructionSuffix.AOffI) != 0 && _isNvidiaDriver)
- {
- string offset = GetTextureOffset(meta, GetOperExpr(op, meta.Offset));
- return "texelFetchOffset(" + sampler + ", " + coords + ", " + lod + ", " + offset + ")." + ch;
- }
-
- return "texelFetch(" + sampler + ", " + coords + ", " + lod + ")." + ch;
- }
-
- private string GetTruncExpr(ShaderIrOp op) => GetUnaryCall(op, "trunc");
-
- private string GetUtofExpr(ShaderIrOp op)
- {
- return "float(uint(" + GetOperExpr(op, op.OperandA) + "))";
- }
-
- private string GetXorExpr(ShaderIrOp op) => GetBinaryExpr(op, "^");
-
- private string GetUnaryCall(ShaderIrOp op, string funcName)
- {
- return funcName + "(" + GetOperExpr(op, op.OperandA) + ")";
- }
-
- private string GetBinaryCall(ShaderIrOp op, string funcName)
- {
- return funcName + "(" + GetOperExpr(op, op.OperandA) + ", " +
- GetOperExpr(op, op.OperandB) + ")";
- }
-
- private string GetTernaryCall(ShaderIrOp op, string funcName)
- {
- return funcName + "(" + GetOperExpr(op, op.OperandA) + ", " +
- GetOperExpr(op, op.OperandB) + ", " +
- GetOperExpr(op, op.OperandC) + ")";
- }
-
- private string GetUnaryExpr(ShaderIrOp op, string opr)
- {
- return opr + GetOperExpr(op, op.OperandA);
- }
-
- private string GetBinaryExpr(ShaderIrOp op, string opr)
- {
- return GetOperExpr(op, op.OperandA) + " " + opr + " " +
- GetOperExpr(op, op.OperandB);
- }
-
- private string GetBinaryExprWithNaN(ShaderIrOp op, string opr)
- {
- string a = GetOperExpr(op, op.OperandA);
- string b = GetOperExpr(op, op.OperandB);
-
- string nanCheck =
- " || isnan(" + a + ")" +
- " || isnan(" + b + ")";
-
- return a + " " + opr + " " + b + nanCheck;
- }
-
- private string GetTernaryExpr(ShaderIrOp op, string opr1, string opr2)
- {
- return GetOperExpr(op, op.OperandA) + " " + opr1 + " " +
- GetOperExpr(op, op.OperandB) + " " + opr2 + " " +
- GetOperExpr(op, op.OperandC);
- }
-
- private string GetTexSamplerName(ShaderIrOp op)
- {
- ShaderIrOperImm node = (ShaderIrOperImm)op.OperandC;
-
- int handle = ((ShaderIrOperImm)op.OperandC).Value;
-
- if (!_decl.Textures.TryGetValue(handle, out ShaderDeclInfo declInfo))
- {
- throw new InvalidOperationException();
- }
-
- return declInfo.Name;
- }
-
- private string GetTexSamplerCoords(ShaderIrOp op)
- {
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- bool hasDepth = (meta.TextureInstructionSuffix & TextureInstructionSuffix.Dc) != 0;
-
- int coords = ImageUtils.GetCoordsCountTextureTarget(meta.TextureTarget);
-
- bool isArray = ImageUtils.IsArray(meta.TextureTarget);
-
-
- string GetLastArgument(ShaderIrNode node)
- {
- string result = GetOperExpr(op, node);
-
- // array index is actually an integer so we need to pass it correctly
- if (isArray)
- {
- result = "float(floatBitsToInt(" + result + "))";
- }
-
- return result;
- }
-
- string lastArgument;
- string depthArgument = "";
-
- int vecSize = coords;
- if (hasDepth && op.Inst != ShaderIrInst.Tld4)
- {
- vecSize++;
- depthArgument = $", {GetOperExpr(op, meta.DepthCompare)}";
- }
-
- switch (coords)
- {
- case 1:
- if (hasDepth)
- {
- return $"vec3({GetOperExpr(op, meta.Coordinates[0])}, 0.0{depthArgument})";
- }
-
- return GetOperExpr(op, meta.Coordinates[0]);
- case 2:
- lastArgument = GetLastArgument(meta.Coordinates[1]);
-
- return $"vec{vecSize}({GetOperExpr(op, meta.Coordinates[0])}, {lastArgument}{depthArgument})";
- case 3:
- lastArgument = GetLastArgument(meta.Coordinates[2]);
-
- return $"vec{vecSize}({GetOperExpr(op, meta.Coordinates[0])}, {GetOperExpr(op, meta.Coordinates[1])}, {lastArgument}{depthArgument})";
- case 4:
- lastArgument = GetLastArgument(meta.Coordinates[3]);
-
- return $"vec4({GetOperExpr(op, meta.Coordinates[0])}, {GetOperExpr(op, meta.Coordinates[1])}, {GetOperExpr(op, meta.Coordinates[2])}, {lastArgument}){depthArgument}";
- default:
- throw new InvalidOperationException();
- }
-
- }
-
- private string GetTextureOffset(ShaderIrMetaTex meta, string oper, int shift = 4, int mask = 0xF)
- {
- string GetOffset(string operation, int index)
- {
- return $"({operation} >> {index * shift}) & 0x{mask:x}";
- }
-
- int coords = ImageUtils.GetCoordsCountTextureTarget(meta.TextureTarget);
-
- if (ImageUtils.IsArray(meta.TextureTarget))
- coords -= 1;
-
- switch (coords)
- {
- case 1:
- return GetOffset(oper, 0);
- case 2:
- return "ivec2(" + GetOffset(oper, 0) + ", " + GetOffset(oper, 1) + ")";
- case 3:
- return "ivec3(" + GetOffset(oper, 0) + ", " + GetOffset(oper, 1) + ", " + GetOffset(oper, 2) + ")";
- case 4:
- return "ivec4(" + GetOffset(oper, 0) + ", " + GetOffset(oper, 1) + ", " + GetOffset(oper, 2) + ", " + GetOffset(oper, 3) + ")";
- default:
- throw new InvalidOperationException();
- }
- }
-
- // TODO: support AOFFI on non nvidia drivers
- private string GetTextureGatherOperation(ShaderIrOp op, string sampler, string coords, string ch)
- {
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- TextureInstructionSuffix suffix = meta.TextureInstructionSuffix;
-
- string chString = "." + ch;
-
- string comp = meta.Component.ToString();
-
- if ((suffix & TextureInstructionSuffix.Dc) != 0)
- {
- comp = GetOperExpr(op, meta.DepthCompare);
- }
-
- if ((suffix & TextureInstructionSuffix.AOffI) != 0 && _isNvidiaDriver)
- {
- string offset = GetTextureOffset(meta, "floatBitsToInt((" + GetOperExpr(op, meta.Offset) + "))", 8, 0x3F);
-
- if ((suffix & TextureInstructionSuffix.Dc) != 0)
- {
- return "textureGatherOffset(" + sampler + ", " + coords + ", " + comp + ", " + offset + ")" + chString;
- }
-
- return "textureGatherOffset(" + sampler + ", " + coords + ", " + offset + ", " + comp + ")" + chString;
- }
- // TODO: Support PTP
- else if ((suffix & TextureInstructionSuffix.Ptp) != 0)
- {
- throw new NotImplementedException();
- }
-
- return "textureGather(" + sampler + ", " + coords + ", " + comp + ")" + chString;
- }
-
- // TODO: support AOFFI on non nvidia drivers
- private string GetTextureOperation(ShaderIrOp op, string sampler, string coords, string ch)
- {
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- TextureInstructionSuffix suffix = meta.TextureInstructionSuffix;
-
- string chString = "." + ch;
-
- if ((suffix & TextureInstructionSuffix.Dc) != 0)
- {
- chString = "";
- }
-
- // TODO: Support LBA and LLA
- if ((suffix & TextureInstructionSuffix.Lz) != 0)
- {
- if ((suffix & TextureInstructionSuffix.AOffI) != 0 && _isNvidiaDriver)
- {
- string offset = GetTextureOffset(meta, "floatBitsToInt((" + GetOperExpr(op, meta.Offset) + "))");
-
- return "textureLodOffset(" + sampler + ", " + coords + ", 0.0, " + offset + ")" + chString;
- }
-
- return "textureLod(" + sampler + ", " + coords + ", 0.0)" + chString;
- }
- else if ((suffix & TextureInstructionSuffix.Lb) != 0)
- {
- if ((suffix & TextureInstructionSuffix.AOffI) != 0 && _isNvidiaDriver)
- {
- string offset = GetTextureOffset(meta, "floatBitsToInt((" + GetOperExpr(op, meta.Offset) + "))");
-
- return "textureOffset(" + sampler + ", " + coords + ", " + offset + ", " + GetOperExpr(op, meta.LevelOfDetail) + ")" + chString;
- }
-
- return "texture(" + sampler + ", " + coords + ", " + GetOperExpr(op, meta.LevelOfDetail) + ")" + chString;
- }
- else if ((suffix & TextureInstructionSuffix.Ll) != 0)
- {
- if ((suffix & TextureInstructionSuffix.AOffI) != 0 && _isNvidiaDriver)
- {
- string offset = GetTextureOffset(meta, "floatBitsToInt((" + GetOperExpr(op, meta.Offset) + "))");
-
- return "textureLodOffset(" + sampler + ", " + coords + ", " + GetOperExpr(op, meta.LevelOfDetail) + ", " + offset + ")" + chString;
- }
-
- return "textureLod(" + sampler + ", " + coords + ", " + GetOperExpr(op, meta.LevelOfDetail) + ")" + chString;
- }
- else if ((suffix & TextureInstructionSuffix.AOffI) != 0 && _isNvidiaDriver)
- {
- string offset = GetTextureOffset(meta, "floatBitsToInt((" + GetOperExpr(op, meta.Offset) + "))");
-
- return "textureOffset(" + sampler + ", " + coords + ", " + offset + ")" + chString;
- }
- else
- {
- return "texture(" + sampler + ", " + coords + ")" + chString;
- }
- throw new NotImplementedException($"Texture Suffix {meta.TextureInstructionSuffix} is not implemented");
-
- }
-
- private string GetITexSamplerCoords(ShaderIrOp op)
- {
- ShaderIrMetaTex meta = (ShaderIrMetaTex)op.MetaData;
-
- switch (ImageUtils.GetCoordsCountTextureTarget(meta.TextureTarget))
- {
- case 1:
- return GetOperExpr(op, meta.Coordinates[0]);
- case 2:
- return "ivec2(" + GetOperExpr(op, meta.Coordinates[0]) + ", " + GetOperExpr(op, meta.Coordinates[1]) + ")";
- case 3:
- return "ivec3(" + GetOperExpr(op, meta.Coordinates[0]) + ", " + GetOperExpr(op, meta.Coordinates[1]) + ", " + GetOperExpr(op, meta.Coordinates[2]) + ")";
- default:
- throw new InvalidOperationException();
- }
- }
-
- private string GetOperExpr(ShaderIrOp op, ShaderIrNode oper)
- {
- return GetExprWithCast(op, oper, GetSrcExpr(oper));
- }
-
- private static string GetExprWithCast(ShaderIrNode dst, ShaderIrNode src, string expr)
- {
- //Note: The "DstType" (of the cast) is the type that the operation
- //uses on the source operands, while the "SrcType" is the destination
- //type of the operand result (if it is a operation) or just the type
- //of the variable for registers/uniforms/attributes.
- OperType dstType = GetSrcNodeType(dst);
- OperType srcType = GetDstNodeType(src);
-
- if (dstType != srcType)
- {
- //Check for invalid casts
- //(like bool to int/float and others).
- if (srcType != OperType.F32 &&
- srcType != OperType.I32)
- {
- throw new InvalidOperationException();
- }
-
- switch (src)
- {
- case ShaderIrOperGpr gpr:
- {
- //When the Gpr is ZR, just return the 0 value directly,
- //since the float encoding for 0 is 0.
- if (gpr.IsConst)
- {
- return "0";
- }
- break;
- }
- }
-
- switch (dstType)
- {
- case OperType.F32: expr = "intBitsToFloat(" + expr + ")"; break;
- case OperType.I32: expr = "floatBitsToInt(" + expr + ")"; break;
- }
- }
-
- return expr;
- }
-
- private static string GetIntConst(int value)
- {
- string expr = value.ToString(CultureInfo.InvariantCulture);
-
- return value < 0 ? "(" + expr + ")" : expr;
- }
-
- private static string GetFloatConst(float value)
- {
- string expr = value.ToString(CultureInfo.InvariantCulture);
-
- return value < 0 ? "(" + expr + ")" : expr;
- }
-
- private static OperType GetDstNodeType(ShaderIrNode node)
- {
- //Special case instructions with the result type different
- //from the input types (like integer <-> float conversion) here.
- if (node is ShaderIrOp op)
- {
- switch (op.Inst)
- {
- case ShaderIrInst.Stof:
- case ShaderIrInst.Txlf:
- case ShaderIrInst.Utof:
- return OperType.F32;
-
- case ShaderIrInst.Ftos:
- case ShaderIrInst.Ftou:
- return OperType.I32;
- }
- }
-
- return GetSrcNodeType(node);
- }
-
- private static OperType GetSrcNodeType(ShaderIrNode node)
- {
- switch (node)
- {
- case ShaderIrOperAbuf abuf:
- return abuf.Offs == GlslDecl.LayerAttr ||
- abuf.Offs == GlslDecl.InstanceIdAttr ||
- abuf.Offs == GlslDecl.VertexIdAttr ||
- abuf.Offs == GlslDecl.FaceAttr
- ? OperType.I32
- : OperType.F32;
-
- case ShaderIrOperCbuf cbuf: return OperType.F32;
- case ShaderIrOperGpr gpr: return OperType.F32;
- case ShaderIrOperImm imm: return OperType.I32;
- case ShaderIrOperImmf immf: return OperType.F32;
- case ShaderIrOperPred pred: return OperType.Bool;
-
- case ShaderIrOp op:
- if (op.Inst > ShaderIrInst.B_Start &&
- op.Inst < ShaderIrInst.B_End)
- {
- return OperType.Bool;
- }
- else if (op.Inst > ShaderIrInst.F_Start &&
- op.Inst < ShaderIrInst.F_End)
- {
- return OperType.F32;
- }
- else if (op.Inst > ShaderIrInst.I_Start &&
- op.Inst < ShaderIrInst.I_End)
- {
- return OperType.I32;
- }
- break;
- }
-
- throw new ArgumentException(nameof(node));
- }
-
- private static string GetBlockPosition(ShaderIrBlock block)
- {
- if (block != null)
- {
- return "0x" + block.Position.ToString("x8") + "u";
- }
- else
- {
- return "0u";
- }
- }
- }
-}
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs
deleted file mode 100644
index be8555d5..00000000
--- a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- public struct GlslProgram
- {
- public string Code { get; private set; }
-
- public IEnumerable<ShaderDeclInfo> Textures { get; private set; }
- public IEnumerable<ShaderDeclInfo> Uniforms { get; private set; }
-
- public GlslProgram(
- string code,
- IEnumerable<ShaderDeclInfo> textures,
- IEnumerable<ShaderDeclInfo> uniforms)
- {
- Code = code;
- Textures = textures;
- Uniforms = uniforms;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
deleted file mode 100644
index f935be74..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
+++ /dev/null
@@ -1,1299 +0,0 @@
-using System;
-
-using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static partial class ShaderDecode
- {
- private enum HalfOutputType
- {
- PackedFp16,
- Fp32,
- MergeH0,
- MergeH1
- }
-
- public static void Bfe_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitBfe(block, opCode, ShaderOper.Cr);
- }
-
- public static void Bfe_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitBfe(block, opCode, ShaderOper.Imm);
- }
-
- public static void Bfe_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitBfe(block, opCode, ShaderOper.Rr);
- }
-
- public static void Fadd_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitFadd(block, opCode, ShaderOper.Cr);
- }
-
- public static void Fadd_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitFadd(block, opCode, ShaderOper.Immf);
- }
-
- public static void Fadd_I32(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode operA = opCode.Gpr8();
- ShaderIrNode operB = opCode.Immf32_20();
-
- bool negB = opCode.Read(53);
- bool absA = opCode.Read(54);
- bool negA = opCode.Read(56);
- bool absB = opCode.Read(57);
-
- operA = GetAluFabsFneg(operA, absA, negA);
- operB = GetAluFabsFneg(operB, absB, negB);
-
- ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Fadd, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- public static void Fadd_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitFadd(block, opCode, ShaderOper.Rr);
- }
-
- public static void Ffma_CR(ShaderIrBlock block, long opCode, int position)
- {
- EmitFfma(block, opCode, ShaderOper.Cr);
- }
-
- public static void Ffma_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitFfma(block, opCode, ShaderOper.Immf);
- }
-
- public static void Ffma_RC(ShaderIrBlock block, long opCode, int position)
- {
- EmitFfma(block, opCode, ShaderOper.Rc);
- }
-
- public static void Ffma_RR(ShaderIrBlock block, long opCode, int position)
- {
- EmitFfma(block, opCode, ShaderOper.Rr);
- }
-
- public static void Fmnmx_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitFmnmx(block, opCode, ShaderOper.Cr);
- }
-
- public static void Fmnmx_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitFmnmx(block, opCode, ShaderOper.Immf);
- }
-
- public static void Fmnmx_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitFmnmx(block, opCode, ShaderOper.Rr);
- }
-
- public static void Fmul_I32(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode operA = opCode.Gpr8();
- ShaderIrNode operB = opCode.Immf32_20();
-
- ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Fmul, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- public static void Fmul_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitFmul(block, opCode, ShaderOper.Cr);
- }
-
- public static void Fmul_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitFmul(block, opCode, ShaderOper.Immf);
- }
-
- public static void Fmul_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitFmul(block, opCode, ShaderOper.Rr);
- }
-
- public static void Fset_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitFset(block, opCode, ShaderOper.Cr);
- }
-
- public static void Fset_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitFset(block, opCode, ShaderOper.Immf);
- }
-
- public static void Fset_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitFset(block, opCode, ShaderOper.Rr);
- }
-
- public static void Fsetp_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitFsetp(block, opCode, ShaderOper.Cr);
- }
-
- public static void Fsetp_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitFsetp(block, opCode, ShaderOper.Immf);
- }
-
- public static void Fsetp_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitFsetp(block, opCode, ShaderOper.Rr);
- }
-
- public static void Hadd2_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitBinaryHalfOp(block, opCode, ShaderIrInst.Fadd);
- }
-
- public static void Hmul2_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitBinaryHalfOp(block, opCode, ShaderIrInst.Fmul);
- }
-
- public static void Iadd_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitIadd(block, opCode, ShaderOper.Cr);
- }
-
- public static void Iadd_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitIadd(block, opCode, ShaderOper.Imm);
- }
-
- public static void Iadd_I32(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode operA = opCode.Gpr8();
- ShaderIrNode operB = opCode.Imm32_20();
-
- bool negA = opCode.Read(56);
-
- operA = GetAluIneg(operA, negA);
-
- ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Add, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- public static void Iadd_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitIadd(block, opCode, ShaderOper.Rr);
- }
-
- public static void Iadd3_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitIadd3(block, opCode, ShaderOper.Cr);
- }
-
- public static void Iadd3_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitIadd3(block, opCode, ShaderOper.Imm);
- }
-
- public static void Iadd3_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitIadd3(block, opCode, ShaderOper.Rr);
- }
-
- public static void Imnmx_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitImnmx(block, opCode, ShaderOper.Cr);
- }
-
- public static void Imnmx_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitImnmx(block, opCode, ShaderOper.Imm);
- }
-
- public static void Imnmx_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitImnmx(block, opCode, ShaderOper.Rr);
- }
-
- public static void Ipa(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode operA = opCode.Abuf28();
- ShaderIrNode operB = opCode.Gpr20();
-
- ShaderIpaMode mode = (ShaderIpaMode)(opCode.Read(54, 3));
-
- ShaderIrMetaIpa meta = new ShaderIrMetaIpa(mode);
-
- ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Ipa, operA, operB, null, meta);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- public static void Iscadd_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitIscadd(block, opCode, ShaderOper.Cr);
- }
-
- public static void Iscadd_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitIscadd(block, opCode, ShaderOper.Imm);
- }
-
- public static void Iscadd_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitIscadd(block, opCode, ShaderOper.Rr);
- }
-
- public static void Iset_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitIset(block, opCode, ShaderOper.Cr);
- }
-
- public static void Iset_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitIset(block, opCode, ShaderOper.Imm);
- }
-
- public static void Iset_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitIset(block, opCode, ShaderOper.Rr);
- }
-
- public static void Isetp_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitIsetp(block, opCode, ShaderOper.Cr);
- }
-
- public static void Isetp_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitIsetp(block, opCode, ShaderOper.Imm);
- }
-
- public static void Isetp_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitIsetp(block, opCode, ShaderOper.Rr);
- }
-
- public static void Lop_I32(ShaderIrBlock block, long opCode, int position)
- {
- int subOp = opCode.Read(53, 3);
-
- bool invA = opCode.Read(55);
- bool invB = opCode.Read(56);
-
- ShaderIrInst inst = 0;
-
- switch (subOp)
- {
- case 0: inst = ShaderIrInst.And; break;
- case 1: inst = ShaderIrInst.Or; break;
- case 2: inst = ShaderIrInst.Xor; break;
- }
-
- ShaderIrNode operB = GetAluNot(opCode.Imm32_20(), invB);
-
- //SubOp == 3 is pass, used by the not instruction
- //which just moves the inverted register value.
- if (subOp < 3)
- {
- ShaderIrNode operA = GetAluNot(opCode.Gpr8(), invA);
-
- ShaderIrOp op = new ShaderIrOp(inst, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
- else
- {
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operB)));
- }
- }
-
- public static void Lop_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitLop(block, opCode, ShaderOper.Cr);
- }
-
- public static void Lop_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitLop(block, opCode, ShaderOper.Imm);
- }
-
- public static void Lop_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitLop(block, opCode, ShaderOper.Rr);
- }
-
- public static void Mufu(ShaderIrBlock block, long opCode, int position)
- {
- int subOp = opCode.Read(20, 0xf);
-
- bool absA = opCode.Read(46);
- bool negA = opCode.Read(48);
-
- ShaderIrInst inst = 0;
-
- switch (subOp)
- {
- case 0: inst = ShaderIrInst.Fcos; break;
- case 1: inst = ShaderIrInst.Fsin; break;
- case 2: inst = ShaderIrInst.Fex2; break;
- case 3: inst = ShaderIrInst.Flg2; break;
- case 4: inst = ShaderIrInst.Frcp; break;
- case 5: inst = ShaderIrInst.Frsq; break;
- case 8: inst = ShaderIrInst.Fsqrt; break;
-
- default: throw new NotImplementedException(subOp.ToString());
- }
-
- ShaderIrNode operA = opCode.Gpr8();
-
- ShaderIrOp op = new ShaderIrOp(inst, GetAluFabsFneg(operA, absA, negA));
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- public static void Psetp(ShaderIrBlock block, long opCode, int position)
- {
- bool negA = opCode.Read(15);
- bool negB = opCode.Read(32);
- bool negP = opCode.Read(42);
-
- ShaderIrInst lopInst = opCode.BLop24();
-
- ShaderIrNode operA = opCode.Pred12();
- ShaderIrNode operB = opCode.Pred29();
-
- if (negA)
- {
- operA = new ShaderIrOp(ShaderIrInst.Bnot, operA);
- }
-
- if (negB)
- {
- operB = new ShaderIrOp(ShaderIrInst.Bnot, operB);
- }
-
- ShaderIrOp op = new ShaderIrOp(lopInst, operA, operB);
-
- ShaderIrOperPred p0Node = opCode.Pred3();
- ShaderIrOperPred p1Node = opCode.Pred0();
- ShaderIrOperPred p2Node = opCode.Pred39();
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op)));
-
- lopInst = opCode.BLop45();
-
- if (lopInst == ShaderIrInst.Band && p1Node.IsConst && p2Node.IsConst)
- {
- return;
- }
-
- ShaderIrNode p2NNode = p2Node;
-
- if (negP)
- {
- p2NNode = new ShaderIrOp(ShaderIrInst.Bnot, p2NNode);
- }
-
- op = new ShaderIrOp(ShaderIrInst.Bnot, p0Node);
-
- op = new ShaderIrOp(lopInst, op, p2NNode);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(p1Node, op)));
-
- op = new ShaderIrOp(lopInst, p0Node, p2NNode);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op)));
- }
-
- public static void Rro_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitRro(block, opCode, ShaderOper.Cr);
- }
-
- public static void Rro_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitRro(block, opCode, ShaderOper.Immf);
- }
-
- public static void Rro_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitRro(block, opCode, ShaderOper.Rr);
- }
-
- public static void Shl_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitAluBinary(block, opCode, ShaderOper.Cr, ShaderIrInst.Lsl);
- }
-
- public static void Shl_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitAluBinary(block, opCode, ShaderOper.Imm, ShaderIrInst.Lsl);
- }
-
- public static void Shl_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitAluBinary(block, opCode, ShaderOper.Rr, ShaderIrInst.Lsl);
- }
-
- public static void Shr_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitAluBinary(block, opCode, ShaderOper.Cr, GetShrInst(opCode));
- }
-
- public static void Shr_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitAluBinary(block, opCode, ShaderOper.Imm, GetShrInst(opCode));
- }
-
- public static void Shr_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitAluBinary(block, opCode, ShaderOper.Rr, GetShrInst(opCode));
- }
-
- private static ShaderIrInst GetShrInst(long opCode)
- {
- bool signed = opCode.Read(48);
-
- return signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
- }
-
- public static void Vmad(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode operA = opCode.Gpr8();
-
- ShaderIrNode operB;
-
- if (opCode.Read(50))
- {
- operB = opCode.Gpr20();
- }
- else
- {
- operB = opCode.Imm19_20();
- }
-
- ShaderIrOperGpr operC = opCode.Gpr39();
-
- ShaderIrNode tmp = new ShaderIrOp(ShaderIrInst.Mul, operA, operB);
-
- ShaderIrNode final = new ShaderIrOp(ShaderIrInst.Add, tmp, operC);
-
- int shr = opCode.Read(51, 3);
-
- if (shr != 0)
- {
- int shift = (shr == 2) ? 15 : 7;
-
- final = new ShaderIrOp(ShaderIrInst.Lsr, final, new ShaderIrOperImm(shift));
- }
-
- block.AddNode(new ShaderIrCmnt("Stubbed. Instruction is reduced to a * b + c"));
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), final)));
- }
-
- public static void Xmad_CR(ShaderIrBlock block, long opCode, int position)
- {
- EmitXmad(block, opCode, ShaderOper.Cr);
- }
-
- public static void Xmad_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitXmad(block, opCode, ShaderOper.Imm);
- }
-
- public static void Xmad_RC(ShaderIrBlock block, long opCode, int position)
- {
- EmitXmad(block, opCode, ShaderOper.Rc);
- }
-
- public static void Xmad_RR(ShaderIrBlock block, long opCode, int position)
- {
- EmitXmad(block, opCode, ShaderOper.Rr);
- }
-
- private static void EmitAluBinary(
- ShaderIrBlock block,
- long opCode,
- ShaderOper oper,
- ShaderIrInst inst)
- {
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- ShaderIrNode op = new ShaderIrOp(inst, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- private static void EmitBfe(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- //TODO: Handle the case where position + length
- //is greater than the word size, in this case the sign bit
- //needs to be replicated to fill the remaining space.
- bool negB = opCode.Read(48);
- bool negA = opCode.Read(49);
-
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- ShaderIrNode op;
-
- bool signed = opCode.Read(48); //?
-
- if (operB is ShaderIrOperImm posLen)
- {
- int position = (posLen.Value >> 0) & 0xff;
- int length = (posLen.Value >> 8) & 0xff;
-
- int lSh = 32 - (position + length);
-
- ShaderIrInst rightShift = signed
- ? ShaderIrInst.Asr
- : ShaderIrInst.Lsr;
-
- op = new ShaderIrOp(ShaderIrInst.Lsl, operA, new ShaderIrOperImm(lSh));
- op = new ShaderIrOp(rightShift, op, new ShaderIrOperImm(lSh + position));
- }
- else
- {
- ShaderIrOperImm shift = new ShaderIrOperImm(8);
- ShaderIrOperImm mask = new ShaderIrOperImm(0xff);
-
- ShaderIrNode opPos, opLen;
-
- opPos = new ShaderIrOp(ShaderIrInst.And, operB, mask);
- opLen = new ShaderIrOp(ShaderIrInst.Lsr, operB, shift);
- opLen = new ShaderIrOp(ShaderIrInst.And, opLen, mask);
-
- op = new ShaderIrOp(ShaderIrInst.Lsr, operA, opPos);
-
- op = ExtendTo32(op, signed, opLen);
- }
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- private static void EmitFadd(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- bool negB = opCode.Read(45);
- bool absA = opCode.Read(46);
- bool negA = opCode.Read(48);
- bool absB = opCode.Read(49);
- bool sat = opCode.Read(50);
-
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- operA = GetAluFabsFneg(operA, absA, negA);
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Immf: operB = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operB = GetAluFabsFneg(operB, absB, negB);
-
- ShaderIrNode op = new ShaderIrOp(ShaderIrInst.Fadd, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), GetAluFsat(op, sat))));
- }
-
- private static void EmitFmul(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- bool negB = opCode.Read(48);
- bool sat = opCode.Read(50);
-
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Immf: operB = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operB = GetAluFneg(operB, negB);
-
- ShaderIrNode op = new ShaderIrOp(ShaderIrInst.Fmul, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), GetAluFsat(op, sat))));
- }
-
- private static void EmitFfma(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- bool negB = opCode.Read(48);
- bool negC = opCode.Read(49);
- bool sat = opCode.Read(50);
-
- ShaderIrNode operA = opCode.Gpr8(), operB, operC;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Immf: operB = opCode.Immf19_20(); break;
- case ShaderOper.Rc: operB = opCode.Gpr39(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operB = GetAluFneg(operB, negB);
-
- if (oper == ShaderOper.Rc)
- {
- operC = GetAluFneg(opCode.Cbuf34(), negC);
- }
- else
- {
- operC = GetAluFneg(opCode.Gpr39(), negC);
- }
-
- ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Ffma, operA, operB, operC);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), GetAluFsat(op, sat))));
- }
-
- private static void EmitIadd(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- ShaderIrNode operA = opCode.Gpr8();
- ShaderIrNode operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- bool negA = opCode.Read(49);
- bool negB = opCode.Read(48);
-
- operA = GetAluIneg(operA, negA);
- operB = GetAluIneg(operB, negB);
-
- ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Add, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- private static void EmitIadd3(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- int mode = opCode.Read(37, 3);
-
- bool neg1 = opCode.Read(51);
- bool neg2 = opCode.Read(50);
- bool neg3 = opCode.Read(49);
-
- int height1 = opCode.Read(35, 3);
- int height2 = opCode.Read(33, 3);
- int height3 = opCode.Read(31, 3);
-
- ShaderIrNode operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- ShaderIrNode ApplyHeight(ShaderIrNode src, int height)
- {
- if (oper != ShaderOper.Rr)
- {
- return src;
- }
-
- switch (height)
- {
- case 0: return src;
- case 1: return new ShaderIrOp(ShaderIrInst.And, src, new ShaderIrOperImm(0xffff));
- case 2: return new ShaderIrOp(ShaderIrInst.Lsr, src, new ShaderIrOperImm(16));
-
- default: throw new InvalidOperationException();
- }
- }
-
- ShaderIrNode src1 = GetAluIneg(ApplyHeight(opCode.Gpr8(), height1), neg1);
- ShaderIrNode src2 = GetAluIneg(ApplyHeight(operB, height2), neg2);
- ShaderIrNode src3 = GetAluIneg(ApplyHeight(opCode.Gpr39(), height3), neg3);
-
- ShaderIrOp sum = new ShaderIrOp(ShaderIrInst.Add, src1, src2);
-
- if (oper == ShaderOper.Rr)
- {
- switch (mode)
- {
- case 1: sum = new ShaderIrOp(ShaderIrInst.Lsr, sum, new ShaderIrOperImm(16)); break;
- case 2: sum = new ShaderIrOp(ShaderIrInst.Lsl, sum, new ShaderIrOperImm(16)); break;
- }
- }
-
- //Note: Here there should be a "+ 1" when carry flag is set
- //but since carry is mostly ignored by other instructions, it's excluded for now
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), new ShaderIrOp(ShaderIrInst.Add, sum, src3))));
- }
-
- private static void EmitIscadd(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- bool negB = opCode.Read(48);
- bool negA = opCode.Read(49);
-
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- ShaderIrOperImm scale = opCode.Imm5_39();
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operA = GetAluIneg(operA, negA);
- operB = GetAluIneg(operB, negB);
-
- ShaderIrOp scaleOp = new ShaderIrOp(ShaderIrInst.Lsl, operA, scale);
- ShaderIrOp addOp = new ShaderIrOp(ShaderIrInst.Add, operB, scaleOp);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), addOp)));
- }
-
- private static void EmitFmnmx(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- EmitMnmx(block, opCode, true, oper);
- }
-
- private static void EmitImnmx(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- EmitMnmx(block, opCode, false, oper);
- }
-
- private static void EmitMnmx(ShaderIrBlock block, long opCode, bool isFloat, ShaderOper oper)
- {
- bool negB = opCode.Read(45);
- bool absA = opCode.Read(46);
- bool negA = opCode.Read(48);
- bool absB = opCode.Read(49);
-
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- if (isFloat)
- {
- operA = GetAluFabsFneg(operA, absA, negA);
- }
- else
- {
- operA = GetAluIabsIneg(operA, absA, negA);
- }
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Immf: operB = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- if (isFloat)
- {
- operB = GetAluFabsFneg(operB, absB, negB);
- }
- else
- {
- operB = GetAluIabsIneg(operB, absB, negB);
- }
-
- ShaderIrOperPred pred = opCode.Pred39();
-
- ShaderIrOp op;
-
- ShaderIrInst maxInst = isFloat ? ShaderIrInst.Fmax : ShaderIrInst.Max;
- ShaderIrInst minInst = isFloat ? ShaderIrInst.Fmin : ShaderIrInst.Min;
-
- if (pred.IsConst)
- {
- bool isMax = opCode.Read(42);
-
- op = new ShaderIrOp(isMax
- ? maxInst
- : minInst, operA, operB);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
- else
- {
- ShaderIrNode predN = opCode.Pred39N();
-
- ShaderIrOp opMax = new ShaderIrOp(maxInst, operA, operB);
- ShaderIrOp opMin = new ShaderIrOp(minInst, operA, operB);
-
- ShaderIrAsg asgMax = new ShaderIrAsg(opCode.Gpr0(), opMax);
- ShaderIrAsg asgMin = new ShaderIrAsg(opCode.Gpr0(), opMin);
-
- block.AddNode(opCode.PredNode(new ShaderIrCond(predN, asgMax, not: true)));
- block.AddNode(opCode.PredNode(new ShaderIrCond(predN, asgMin, not: false)));
- }
- }
-
- private static void EmitRro(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- //Note: this is a range reduction instruction and is supposed to
- //be used with Mufu, here it just moves the value and ignores the operation.
- bool negA = opCode.Read(45);
- bool absA = opCode.Read(49);
-
- ShaderIrNode operA;
-
- switch (oper)
- {
- case ShaderOper.Cr: operA = opCode.Cbuf34(); break;
- case ShaderOper.Immf: operA = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operA = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operA = GetAluFabsFneg(operA, absA, negA);
-
- block.AddNode(new ShaderIrCmnt("Stubbed."));
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operA)));
- }
-
- private static void EmitFset(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- EmitSet(block, opCode, true, oper);
- }
-
- private static void EmitIset(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- EmitSet(block, opCode, false, oper);
- }
-
- private static void EmitSet(ShaderIrBlock block, long opCode, bool isFloat, ShaderOper oper)
- {
- bool negA = opCode.Read(43);
- bool absB = opCode.Read(44);
- bool negB = opCode.Read(53);
- bool absA = opCode.Read(54);
-
- bool boolFloat = opCode.Read(isFloat ? 52 : 44);
-
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Immf: operB = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- ShaderIrInst cmpInst;
-
- if (isFloat)
- {
- operA = GetAluFabsFneg(operA, absA, negA);
- operB = GetAluFabsFneg(operB, absB, negB);
-
- cmpInst = opCode.CmpF();
- }
- else
- {
- cmpInst = opCode.Cmp();
- }
-
- ShaderIrOp op = new ShaderIrOp(cmpInst, operA, operB);
-
- ShaderIrInst lopInst = opCode.BLop45();
-
- ShaderIrOperPred pNode = opCode.Pred39();
-
- ShaderIrNode imm0, imm1;
-
- if (boolFloat)
- {
- imm0 = new ShaderIrOperImmf(0);
- imm1 = new ShaderIrOperImmf(1);
- }
- else
- {
- imm0 = new ShaderIrOperImm(0);
- imm1 = new ShaderIrOperImm(-1);
- }
-
- ShaderIrNode asg0 = new ShaderIrAsg(opCode.Gpr0(), imm0);
- ShaderIrNode asg1 = new ShaderIrAsg(opCode.Gpr0(), imm1);
-
- if (lopInst != ShaderIrInst.Band || !pNode.IsConst)
- {
- ShaderIrOp op2 = new ShaderIrOp(lopInst, op, pNode);
-
- asg0 = new ShaderIrCond(op2, asg0, not: true);
- asg1 = new ShaderIrCond(op2, asg1, not: false);
- }
- else
- {
- asg0 = new ShaderIrCond(op, asg0, not: true);
- asg1 = new ShaderIrCond(op, asg1, not: false);
- }
-
- block.AddNode(opCode.PredNode(asg0));
- block.AddNode(opCode.PredNode(asg1));
- }
-
- private static void EmitFsetp(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- EmitSetp(block, opCode, true, oper);
- }
-
- private static void EmitIsetp(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- EmitSetp(block, opCode, false, oper);
- }
-
- private static void EmitSetp(ShaderIrBlock block, long opCode, bool isFloat, ShaderOper oper)
- {
- bool absA = opCode.Read(7);
- bool negP = opCode.Read(42);
- bool negA = opCode.Read(43);
- bool absB = opCode.Read(44);
-
- ShaderIrNode operA = opCode.Gpr8(), operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Immf: operB = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- ShaderIrInst cmpInst;
-
- if (isFloat)
- {
- operA = GetAluFabsFneg(operA, absA, negA);
- operB = GetAluFabs (operB, absB);
-
- cmpInst = opCode.CmpF();
- }
- else
- {
- cmpInst = opCode.Cmp();
- }
-
- ShaderIrOp op = new ShaderIrOp(cmpInst, operA, operB);
-
- ShaderIrOperPred p0Node = opCode.Pred3();
- ShaderIrOperPred p1Node = opCode.Pred0();
- ShaderIrOperPred p2Node = opCode.Pred39();
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op)));
-
- ShaderIrInst lopInst = opCode.BLop45();
-
- if (lopInst == ShaderIrInst.Band && p1Node.IsConst && p2Node.IsConst)
- {
- return;
- }
-
- ShaderIrNode p2NNode = p2Node;
-
- if (negP)
- {
- p2NNode = new ShaderIrOp(ShaderIrInst.Bnot, p2NNode);
- }
-
- op = new ShaderIrOp(ShaderIrInst.Bnot, p0Node);
-
- op = new ShaderIrOp(lopInst, op, p2NNode);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(p1Node, op)));
-
- op = new ShaderIrOp(lopInst, p0Node, p2NNode);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(p0Node, op)));
- }
-
- private static void EmitBinaryHalfOp(ShaderIrBlock block, long opCode, ShaderIrInst inst)
- {
- bool absB = opCode.Read(30);
- bool negB = opCode.Read(31);
- bool sat = opCode.Read(32);
- bool absA = opCode.Read(44);
-
- ShaderIrOperGpr[] vecA = opCode.GprHalfVec8();
- ShaderIrOperGpr[] vecB = opCode.GprHalfVec20();
-
- HalfOutputType outputType = (HalfOutputType)opCode.Read(49, 3);
-
- int elems = outputType == HalfOutputType.PackedFp16 ? 2 : 1;
- int first = outputType == HalfOutputType.MergeH1 ? 1 : 0;
-
- for (int index = first; index < elems; index++)
- {
- ShaderIrNode operA = GetAluFabs (vecA[index], absA);
- ShaderIrNode operB = GetAluFabsFneg(vecB[index], absB, negB);
-
- ShaderIrNode op = new ShaderIrOp(inst, operA, operB);
-
- ShaderIrOperGpr dst = GetHalfDst(opCode, outputType, index);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(dst, GetAluFsat(op, sat))));
- }
- }
-
- private static ShaderIrOperGpr GetHalfDst(long opCode, HalfOutputType outputType, int index)
- {
- switch (outputType)
- {
- case HalfOutputType.PackedFp16: return opCode.GprHalf0(index);
- case HalfOutputType.Fp32: return opCode.Gpr0();
- case HalfOutputType.MergeH0: return opCode.GprHalf0(0);
- case HalfOutputType.MergeH1: return opCode.GprHalf0(1);
- }
-
- throw new ArgumentException(nameof(outputType));
- }
-
- private static void EmitLop(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- int subOp = opCode.Read(41, 3);
-
- bool invA = opCode.Read(39);
- bool invB = opCode.Read(40);
-
- ShaderIrInst inst = 0;
-
- switch (subOp)
- {
- case 0: inst = ShaderIrInst.And; break;
- case 1: inst = ShaderIrInst.Or; break;
- case 2: inst = ShaderIrInst.Xor; break;
- }
-
- ShaderIrNode operA = GetAluNot(opCode.Gpr8(), invA);
- ShaderIrNode operB;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.Imm19_20(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operB = GetAluNot(operB, invB);
-
- ShaderIrNode op;
-
- if (subOp < 3)
- {
- op = new ShaderIrOp(inst, operA, operB);
- }
- else
- {
- op = operB;
- }
-
- ShaderIrNode compare = new ShaderIrOp(ShaderIrInst.Cne, op, new ShaderIrOperImm(0));
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Pred48(), compare)));
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- private enum XmadMode
- {
- Cfull = 0,
- Clo = 1,
- Chi = 2,
- Csfu = 3,
- Cbcc = 4
- }
-
- private static void EmitXmad(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- bool signedA = opCode.Read(48);
- bool signedB = opCode.Read(49);
- bool highB = opCode.Read(52);
- bool highA = opCode.Read(53);
-
- int mode = opCode.Read(50, 7);
-
- ShaderIrNode operA = opCode.Gpr8(), operB, operC;
-
- switch (oper)
- {
- case ShaderOper.Cr: operB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operB = opCode.ImmU16_20(); break;
- case ShaderOper.Rc: operB = opCode.Gpr39(); break;
- case ShaderOper.Rr: operB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- ShaderIrNode operB2 = operB;
-
- if (oper == ShaderOper.Imm)
- {
- int imm = ((ShaderIrOperImm)operB2).Value;
-
- if (!highB)
- {
- imm <<= 16;
- }
-
- if (signedB)
- {
- imm >>= 16;
- }
- else
- {
- imm = (int)((uint)imm >> 16);
- }
-
- operB2 = new ShaderIrOperImm(imm);
- }
-
- ShaderIrOperImm imm16 = new ShaderIrOperImm(16);
-
- //If we are working with the lower 16-bits of the A/B operands,
- //we need to shift the lower 16-bits to the top 16-bits. Later,
- //they will be right shifted. For U16 types, this will be a logical
- //right shift, and for S16 types, a arithmetic right shift.
- if (!highA)
- {
- operA = new ShaderIrOp(ShaderIrInst.Lsl, operA, imm16);
- }
-
- if (!highB && oper != ShaderOper.Imm)
- {
- operB2 = new ShaderIrOp(ShaderIrInst.Lsl, operB2, imm16);
- }
-
- ShaderIrInst shiftA = signedA ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
- ShaderIrInst shiftB = signedB ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
-
- operA = new ShaderIrOp(shiftA, operA, imm16);
-
- if (oper != ShaderOper.Imm)
- {
- operB2 = new ShaderIrOp(shiftB, operB2, imm16);
- }
-
- bool productShiftLeft = false;
- bool merge = false;
-
- if (oper == ShaderOper.Rc)
- {
- operC = opCode.Cbuf34();
- }
- else
- {
- operC = opCode.Gpr39();
-
- productShiftLeft = opCode.Read(36);
- merge = opCode.Read(37);
- }
-
- ShaderIrOp mulOp = new ShaderIrOp(ShaderIrInst.Mul, operA, operB2);
-
- if (productShiftLeft)
- {
- mulOp = new ShaderIrOp(ShaderIrInst.Lsl, mulOp, imm16);
- }
-
- switch ((XmadMode)mode)
- {
- case XmadMode.Clo: operC = ExtendTo32(operC, signed: false, size: 16); break;
-
- case XmadMode.Chi: operC = new ShaderIrOp(ShaderIrInst.Lsr, operC, imm16); break;
-
- case XmadMode.Cbcc:
- {
- ShaderIrOp operBLsh16 = new ShaderIrOp(ShaderIrInst.Lsl, operB, imm16);
-
- operC = new ShaderIrOp(ShaderIrInst.Add, operC, operBLsh16);
-
- break;
- }
-
- case XmadMode.Csfu:
- {
- ShaderIrOperImm imm31 = new ShaderIrOperImm(31);
-
- ShaderIrOp signAdjustA = new ShaderIrOp(ShaderIrInst.Lsr, operA, imm31);
- ShaderIrOp signAdjustB = new ShaderIrOp(ShaderIrInst.Lsr, operB2, imm31);
-
- signAdjustA = new ShaderIrOp(ShaderIrInst.Lsl, signAdjustA, imm16);
- signAdjustB = new ShaderIrOp(ShaderIrInst.Lsl, signAdjustB, imm16);
-
- ShaderIrOp signAdjust = new ShaderIrOp(ShaderIrInst.Add, signAdjustA, signAdjustB);
-
- operC = new ShaderIrOp(ShaderIrInst.Sub, operC, signAdjust);
-
- break;
- }
- }
-
- ShaderIrOp addOp = new ShaderIrOp(ShaderIrInst.Add, mulOp, operC);
-
- if (merge)
- {
- ShaderIrOperImm imm16Mask = new ShaderIrOperImm(0xffff);
-
- addOp = new ShaderIrOp(ShaderIrInst.And, addOp, imm16Mask);
- operB = new ShaderIrOp(ShaderIrInst.Lsl, operB, imm16);
- addOp = new ShaderIrOp(ShaderIrInst.Or, addOp, operB);
- }
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), addOp)));
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
deleted file mode 100644
index fc992693..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static partial class ShaderDecode
- {
- public static void Bra(ShaderIrBlock block, long opCode, int position)
- {
- if ((opCode & 0x20) != 0)
- {
- //This reads the target offset from the constant buffer.
- //Almost impossible to support with GLSL.
- throw new NotImplementedException();
- }
-
- ShaderIrOperImm imm = new ShaderIrOperImm(position + opCode.Branch());
-
- block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Bra, imm)));
- }
-
- public static void Exit(ShaderIrBlock block, long opCode, int position)
- {
- int cCode = (int)opCode & 0x1f;
-
- //TODO: Figure out what the other condition codes mean...
- if (cCode == 0xf)
- {
- block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Exit)));
- }
- }
-
- public static void Kil(ShaderIrBlock block, long opCode, int position)
- {
- block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Kil)));
- }
-
- public static void Ssy(ShaderIrBlock block, long opCode, int position)
- {
- if ((opCode & 0x20) != 0)
- {
- //This reads the target offset from the constant buffer.
- //Almost impossible to support with GLSL.
- throw new NotImplementedException();
- }
-
- ShaderIrOperImm imm = new ShaderIrOperImm(position + opCode.Branch());
-
- block.AddNode(new ShaderIrOp(ShaderIrInst.Ssy, imm));
- }
-
- public static void Sync(ShaderIrBlock block, long opCode, int position)
- {
- //TODO: Implement Sync condition codes
- block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Sync)));
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs
deleted file mode 100644
index cc385aa4..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- delegate void ShaderDecodeFunc(ShaderIrBlock block, long opCode, int position);
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs
deleted file mode 100644
index 9a84e612..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static class ShaderDecodeHelper
- {
- private static readonly ShaderIrOperImmf ImmfZero = new ShaderIrOperImmf(0);
- private static readonly ShaderIrOperImmf ImmfOne = new ShaderIrOperImmf(1);
-
- public static ShaderIrNode GetAluFabsFneg(ShaderIrNode node, bool abs, bool neg)
- {
- return GetAluFneg(GetAluFabs(node, abs), neg);
- }
-
- public static ShaderIrNode GetAluFabs(ShaderIrNode node, bool abs)
- {
- return abs ? new ShaderIrOp(ShaderIrInst.Fabs, node) : node;
- }
-
- public static ShaderIrNode GetAluFneg(ShaderIrNode node, bool neg)
- {
- return neg ? new ShaderIrOp(ShaderIrInst.Fneg, node) : node;
- }
-
- public static ShaderIrNode GetAluFsat(ShaderIrNode node, bool sat)
- {
- return sat ? new ShaderIrOp(ShaderIrInst.Fclamp, node, ImmfZero, ImmfOne) : node;
- }
-
- public static ShaderIrNode GetAluIabsIneg(ShaderIrNode node, bool abs, bool neg)
- {
- return GetAluIneg(GetAluIabs(node, abs), neg);
- }
-
- public static ShaderIrNode GetAluIabs(ShaderIrNode node, bool abs)
- {
- return abs ? new ShaderIrOp(ShaderIrInst.Abs, node) : node;
- }
-
- public static ShaderIrNode GetAluIneg(ShaderIrNode node, bool neg)
- {
- return neg ? new ShaderIrOp(ShaderIrInst.Neg, node) : node;
- }
-
- public static ShaderIrNode GetAluNot(ShaderIrNode node, bool not)
- {
- return not ? new ShaderIrOp(ShaderIrInst.Not, node) : node;
- }
-
- public static ShaderIrNode ExtendTo32(ShaderIrNode node, bool signed, int size)
- {
- int shift = 32 - size;
-
- ShaderIrInst rightShift = signed
- ? ShaderIrInst.Asr
- : ShaderIrInst.Lsr;
-
- node = new ShaderIrOp(ShaderIrInst.Lsl, node, new ShaderIrOperImm(shift));
- node = new ShaderIrOp(rightShift, node, new ShaderIrOperImm(shift));
-
- return node;
- }
-
- public static ShaderIrNode ExtendTo32(ShaderIrNode node, bool signed, ShaderIrNode size)
- {
- ShaderIrOperImm wordSize = new ShaderIrOperImm(32);
-
- ShaderIrOp shift = new ShaderIrOp(ShaderIrInst.Sub, wordSize, size);
-
- ShaderIrInst rightShift = signed
- ? ShaderIrInst.Asr
- : ShaderIrInst.Lsr;
-
- node = new ShaderIrOp(ShaderIrInst.Lsl, node, shift);
- node = new ShaderIrOp(rightShift, node, shift);
-
- return node;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
deleted file mode 100644
index 7ce126b0..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
+++ /dev/null
@@ -1,878 +0,0 @@
-using Ryujinx.Graphics.Texture;
-using System;
-
-using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static partial class ShaderDecode
- {
- // ReSharper disable InconsistentNaming
- private const int ____ = 0x0;
- private const int R___ = 0x1;
- private const int _G__ = 0x2;
- private const int RG__ = 0x3;
- private const int __B_ = 0x4;
- private const int RGB_ = 0x7;
- private const int ___A = 0x8;
- private const int R__A = 0x9;
- private const int _G_A = 0xa;
- private const int RG_A = 0xb;
- private const int __BA = 0xc;
- private const int R_BA = 0xd;
- private const int _GBA = 0xe;
- private const int RGBA = 0xf;
- // ReSharper restore InconsistentNaming
-
- private static int[,] _maskLut = new int[,]
- {
- { ____, ____, ____, ____, ____, ____, ____, ____ },
- { R___, _G__, __B_, ___A, RG__, R__A, _G_A, __BA },
- { R___, _G__, __B_, ___A, RG__, ____, ____, ____ },
- { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ }
- };
-
- private static GalTextureTarget TexToTextureTarget(int texType, bool isArray)
- {
- switch (texType)
- {
- case 0:
- return isArray ? GalTextureTarget.OneDArray : GalTextureTarget.OneD;
- case 2:
- return isArray ? GalTextureTarget.TwoDArray : GalTextureTarget.TwoD;
- case 4:
- if (isArray)
- throw new InvalidOperationException("ARRAY bit set on a TEX with 3D texture!");
- return GalTextureTarget.ThreeD;
- case 6:
- return isArray ? GalTextureTarget.CubeArray : GalTextureTarget.CubeMap;
- default:
- throw new InvalidOperationException();
- }
- }
-
- private static GalTextureTarget TexsToTextureTarget(int texType)
- {
- switch (texType)
- {
- case 0:
- return GalTextureTarget.OneD;
- case 2:
- case 4:
- case 6:
- case 8:
- case 0xa:
- case 0xc:
- return GalTextureTarget.TwoD;
- case 0xe:
- case 0x10:
- case 0x12:
- return GalTextureTarget.TwoDArray;
- case 0x14:
- case 0x16:
- return GalTextureTarget.ThreeD;
- case 0x18:
- case 0x1a:
- return GalTextureTarget.CubeMap;
- default:
- throw new InvalidOperationException();
- }
- }
-
- public static GalTextureTarget TldsToTextureTarget(int texType)
- {
- switch (texType)
- {
- case 0:
- case 2:
- return GalTextureTarget.OneD;
- case 4:
- case 8:
- case 0xa:
- case 0xc:
- case 0x18:
- return GalTextureTarget.TwoD;
- case 0x10:
- return GalTextureTarget.TwoDArray;
- case 0xe:
- return GalTextureTarget.ThreeD;
- default:
- throw new InvalidOperationException();
- }
- }
-
- public static void Ld_A(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode[] opers = opCode.Abuf20();
-
- //Used by GS
- ShaderIrOperGpr vertex = opCode.Gpr39();
-
- int index = 0;
-
- foreach (ShaderIrNode operA in opers)
- {
- ShaderIrOperGpr operD = opCode.Gpr0();
-
- operD.Index += index++;
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(operD, operA)));
- }
- }
-
- public static void Ld_C(ShaderIrBlock block, long opCode, int position)
- {
- int cbufPos = opCode.Read(22, 0x3fff);
- int cbufIndex = opCode.Read(36, 0x1f);
- int type = opCode.Read(48, 7);
-
- if (type > 5)
- {
- throw new InvalidOperationException();
- }
-
- ShaderIrOperGpr temp = ShaderIrOperGpr.MakeTemporary();
-
- block.AddNode(new ShaderIrAsg(temp, opCode.Gpr8()));
-
- int count = type == 5 ? 2 : 1;
-
- for (int index = 0; index < count; index++)
- {
- ShaderIrOperCbuf operA = new ShaderIrOperCbuf(cbufIndex, cbufPos, temp);
-
- ShaderIrOperGpr operD = opCode.Gpr0();
-
- operA.Pos += index;
- operD.Index += index;
-
- if (!operD.IsValidRegister)
- {
- break;
- }
-
- ShaderIrNode node = operA;
-
- if (type < 4)
- {
- //This is a 8 or 16 bits type.
- bool signed = (type & 1) != 0;
-
- int size = 8 << (type >> 1);
-
- node = ExtendTo32(node, signed, size);
- }
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(operD, node)));
- }
- }
-
- public static void St_A(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode[] opers = opCode.Abuf20();
-
- int index = 0;
-
- foreach (ShaderIrNode operA in opers)
- {
- ShaderIrOperGpr operD = opCode.Gpr0();
-
- operD.Index += index++;
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(operA, operD)));
- }
- }
-
- public static void Texq(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrNode operD = opCode.Gpr0();
- ShaderIrNode operA = opCode.Gpr8();
-
- ShaderTexqInfo info = (ShaderTexqInfo)(opCode.Read(22, 0x1f));
-
- ShaderIrMetaTexq meta0 = new ShaderIrMetaTexq(info, 0);
- ShaderIrMetaTexq meta1 = new ShaderIrMetaTexq(info, 1);
-
- ShaderIrNode operC = opCode.Imm13_36();
-
- ShaderIrOp op0 = new ShaderIrOp(ShaderIrInst.Texq, operA, null, operC, meta0);
- ShaderIrOp op1 = new ShaderIrOp(ShaderIrInst.Texq, operA, null, operC, meta1);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(operD, op0)));
- block.AddNode(opCode.PredNode(new ShaderIrAsg(operA, op1))); //Is this right?
- }
-
- public static void Tex(ShaderIrBlock block, long opCode, int position)
- {
- TextureInstructionSuffix suffix;
-
- int rawSuffix = opCode.Read(0x34, 0x38);
-
- switch (rawSuffix)
- {
- case 0:
- suffix = TextureInstructionSuffix.None;
- break;
- case 0x8:
- suffix = TextureInstructionSuffix.Lz;
- break;
- case 0x10:
- suffix = TextureInstructionSuffix.Lb;
- break;
- case 0x18:
- suffix = TextureInstructionSuffix.Ll;
- break;
- case 0x30:
- suffix = TextureInstructionSuffix.Lba;
- break;
- case 0x38:
- suffix = TextureInstructionSuffix.Lla;
- break;
- default:
- throw new InvalidOperationException($"Invalid Suffix for TEX instruction {rawSuffix}");
- }
-
- bool isOffset = opCode.Read(0x36);
-
- if (isOffset)
- suffix |= TextureInstructionSuffix.AOffI;
-
- EmitTex(block, opCode, suffix, gprHandle: false);
- }
-
- public static void Tex_B(ShaderIrBlock block, long opCode, int position)
- {
- TextureInstructionSuffix suffix;
-
- int rawSuffix = opCode.Read(0x24, 0xe);
-
- switch (rawSuffix)
- {
- case 0:
- suffix = TextureInstructionSuffix.None;
- break;
- case 0x2:
- suffix = TextureInstructionSuffix.Lz;
- break;
- case 0x4:
- suffix = TextureInstructionSuffix.Lb;
- break;
- case 0x6:
- suffix = TextureInstructionSuffix.Ll;
- break;
- case 0xc:
- suffix = TextureInstructionSuffix.Lba;
- break;
- case 0xe:
- suffix = TextureInstructionSuffix.Lla;
- break;
- default:
- throw new InvalidOperationException($"Invalid Suffix for TEX.B instruction {rawSuffix}");
- }
-
- bool isOffset = opCode.Read(0x23);
-
- if (isOffset)
- suffix |= TextureInstructionSuffix.AOffI;
-
- EmitTex(block, opCode, suffix, gprHandle: true);
- }
-
- private static void EmitTex(ShaderIrBlock block, long opCode, TextureInstructionSuffix textureInstructionSuffix, bool gprHandle)
- {
- bool isArray = opCode.HasArray();
-
- GalTextureTarget textureTarget = TexToTextureTarget(opCode.Read(28, 6), isArray);
-
- bool hasDepthCompare = opCode.Read(0x32);
-
- if (hasDepthCompare)
- {
- textureInstructionSuffix |= TextureInstructionSuffix.Dc;
- }
-
- ShaderIrOperGpr[] coords = new ShaderIrOperGpr[ImageUtils.GetCoordsCountTextureTarget(textureTarget)];
-
- int indexExtraCoord = 0;
-
- if (isArray)
- {
- indexExtraCoord++;
-
- coords[coords.Length - 1] = opCode.Gpr8();
- }
-
-
- for (int index = 0; index < coords.Length - indexExtraCoord; index++)
- {
- ShaderIrOperGpr coordReg = opCode.Gpr8();
-
- coordReg.Index += index;
-
- coordReg.Index += indexExtraCoord;
-
- if (!coordReg.IsValidRegister)
- {
- coordReg.Index = ShaderIrOperGpr.ZrIndex;
- }
-
- coords[index] = coordReg;
- }
-
- int chMask = opCode.Read(31, 0xf);
-
- ShaderIrOperGpr levelOfDetail = null;
- ShaderIrOperGpr offset = null;
- ShaderIrOperGpr depthCompare = null;
-
- // TODO: determine first argument when TEX.B is used
- int operBIndex = gprHandle ? 1 : 0;
-
- if ((textureInstructionSuffix & TextureInstructionSuffix.Ll) != 0 ||
- (textureInstructionSuffix & TextureInstructionSuffix.Lb) != 0 ||
- (textureInstructionSuffix & TextureInstructionSuffix.Lba) != 0 ||
- (textureInstructionSuffix & TextureInstructionSuffix.Lla) != 0)
- {
- levelOfDetail = opCode.Gpr20();
- levelOfDetail.Index += operBIndex;
-
- operBIndex++;
- }
-
- if ((textureInstructionSuffix & TextureInstructionSuffix.AOffI) != 0)
- {
- offset = opCode.Gpr20();
- offset.Index += operBIndex;
-
- operBIndex++;
- }
-
- if ((textureInstructionSuffix & TextureInstructionSuffix.Dc) != 0)
- {
- depthCompare = opCode.Gpr20();
- depthCompare.Index += operBIndex;
-
- operBIndex++;
- }
-
- // ???
- ShaderIrNode operC = gprHandle
- ? (ShaderIrNode)opCode.Gpr20()
- : (ShaderIrNode)opCode.Imm13_36();
-
- ShaderIrInst inst = gprHandle ? ShaderIrInst.Texb : ShaderIrInst.Texs;
-
- coords = CoordsRegistersToTempRegisters(block, coords);
-
- int regInc = 0;
-
- for (int ch = 0; ch < 4; ch++)
- {
- if (!IsChannelUsed(chMask, ch))
- {
- continue;
- }
-
- ShaderIrOperGpr dst = opCode.Gpr0();
-
- dst.Index += regInc++;
-
- if (!dst.IsValidRegister || dst.IsConst)
- {
- continue;
- }
-
- ShaderIrMetaTex meta = new ShaderIrMetaTex(ch, textureTarget, textureInstructionSuffix, coords)
- {
- LevelOfDetail = levelOfDetail,
- Offset = offset,
- DepthCompare = depthCompare
- };
-
- ShaderIrOp op = new ShaderIrOp(inst, coords[0], coords.Length > 1 ? coords[1] : null, operC, meta);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(dst, op)));
- }
- }
-
- public static void Texs(ShaderIrBlock block, long opCode, int position)
- {
- TextureInstructionSuffix suffix;
-
- int rawSuffix = opCode.Read(0x34, 0x1e);
-
- switch (rawSuffix)
- {
- case 0:
- case 0x4:
- case 0x10:
- case 0x16:
- suffix = TextureInstructionSuffix.Lz;
- break;
- case 0x6:
- case 0x1a:
- suffix = TextureInstructionSuffix.Ll;
- break;
- case 0x8:
- suffix = TextureInstructionSuffix.Dc;
- break;
- case 0x2:
- case 0xe:
- case 0x14:
- case 0x18:
- suffix = TextureInstructionSuffix.None;
- break;
- case 0xa:
- suffix = TextureInstructionSuffix.Ll | TextureInstructionSuffix.Dc;
- break;
- case 0xc:
- case 0x12:
- suffix = TextureInstructionSuffix.Lz | TextureInstructionSuffix.Dc;
- break;
- default:
- throw new InvalidOperationException($"Invalid Suffix for TEXS instruction {rawSuffix}");
- }
-
- GalTextureTarget textureTarget = TexsToTextureTarget(opCode.Read(52, 0x1e));
-
- EmitTexs(block, opCode, ShaderIrInst.Texs, textureTarget, suffix);
- }
-
- public static void Tlds(ShaderIrBlock block, long opCode, int position)
- {
- TextureInstructionSuffix suffix;
-
- int rawSuffix = opCode.Read(0x34, 0x1e);
-
- switch (rawSuffix)
- {
- case 0:
- case 0x4:
- case 0x8:
- suffix = TextureInstructionSuffix.Lz | TextureInstructionSuffix.AOffI;
- break;
- case 0xc:
- suffix = TextureInstructionSuffix.Lz | TextureInstructionSuffix.Mz;
- break;
- case 0xe:
- case 0x10:
- suffix = TextureInstructionSuffix.Lz;
- break;
- case 0x2:
- case 0xa:
- suffix = TextureInstructionSuffix.Ll;
- break;
- case 0x18:
- suffix = TextureInstructionSuffix.Ll | TextureInstructionSuffix.AOffI;
- break;
- default:
- throw new InvalidOperationException($"Invalid Suffix for TLDS instruction {rawSuffix}");
- }
-
- GalTextureTarget textureTarget = TldsToTextureTarget(opCode.Read(52, 0x1e));
-
- EmitTexs(block, opCode, ShaderIrInst.Txlf, textureTarget, suffix);
- }
-
- public static void Tld4(ShaderIrBlock block, long opCode, int position)
- {
- TextureInstructionSuffix suffix;
-
- int rawSuffix = opCode.Read(0x34, 0xc);
-
- switch (rawSuffix)
- {
- case 0:
- suffix = TextureInstructionSuffix.None;
- break;
- case 0x4:
- suffix = TextureInstructionSuffix.AOffI;
- break;
- case 0x8:
- suffix = TextureInstructionSuffix.Ptp;
- break;
- default:
- throw new InvalidOperationException($"Invalid Suffix for TLD4 instruction {rawSuffix}");
- }
-
- bool isShadow = opCode.Read(0x32);
-
- bool isArray = opCode.HasArray();
- int chMask = opCode.Read(31, 0xf);
-
- GalTextureTarget textureTarget = TexToTextureTarget(opCode.Read(28, 6), isArray);
-
- if (isShadow)
- {
- suffix |= TextureInstructionSuffix.Dc;
- }
-
- EmitTld4(block, opCode, textureTarget, suffix, chMask, opCode.Read(0x38, 0x3), false);
- }
-
- public static void Tld4S(ShaderIrBlock block, long opCode, int position)
- {
- TextureInstructionSuffix suffix = TextureInstructionSuffix.None;
-
- bool isOffset = opCode.Read(0x33);
- bool isShadow = opCode.Read(0x32);
-
- if (isOffset)
- {
- suffix |= TextureInstructionSuffix.AOffI;
- }
-
- if (isShadow)
- {
- suffix |= TextureInstructionSuffix.Dc;
- }
-
- // TLD4S seems to only support 2D textures with RGBA mask?
- EmitTld4(block, opCode, GalTextureTarget.TwoD, suffix, RGBA, opCode.Read(0x34, 0x3), true);
- }
-
- private static void EmitTexs(ShaderIrBlock block,
- long opCode,
- ShaderIrInst inst,
- GalTextureTarget textureTarget,
- TextureInstructionSuffix textureInstructionSuffix)
- {
- if (inst == ShaderIrInst.Txlf && textureTarget == GalTextureTarget.CubeArray)
- {
- throw new InvalidOperationException("TLDS instructions cannot use CUBE modifier!");
- }
-
- bool isArray = ImageUtils.IsArray(textureTarget);
-
- ShaderIrOperGpr[] coords = new ShaderIrOperGpr[ImageUtils.GetCoordsCountTextureTarget(textureTarget)];
-
- ShaderIrOperGpr operA = opCode.Gpr8();
- ShaderIrOperGpr operB = opCode.Gpr20();
-
- ShaderIrOperGpr suffixExtra = opCode.Gpr20();
- suffixExtra.Index += 1;
-
- int coordStartIndex = 0;
-
- if (isArray)
- {
- coordStartIndex++;
- coords[coords.Length - 1] = opCode.Gpr8();
- }
-
- switch (coords.Length - coordStartIndex)
- {
- case 1:
- coords[0] = opCode.Gpr8();
-
- break;
- case 2:
- coords[0] = opCode.Gpr8();
- coords[0].Index += coordStartIndex;
-
- break;
- case 3:
- coords[0] = opCode.Gpr8();
- coords[0].Index += coordStartIndex;
-
- coords[1] = opCode.Gpr8();
- coords[1].Index += 1 + coordStartIndex;
-
- break;
- default:
- throw new NotSupportedException($"{coords.Length - coordStartIndex} coords textures aren't supported in TEXS");
- }
-
- int operBIndex = 0;
-
- ShaderIrOperGpr levelOfDetail = null;
- ShaderIrOperGpr offset = null;
- ShaderIrOperGpr depthCompare = null;
-
- // OperB is always the last value
- // Not applicable to 1d textures
- if (coords.Length - coordStartIndex != 1)
- {
- coords[coords.Length - coordStartIndex - 1] = operB;
- operBIndex++;
- }
-
- // Encoding of TEXS/TLDS is a bit special and change for 2d textures
- // NOTE: OperA seems to hold at best two args.
- // On 2D textures, if no suffix need an additional values, Y is stored in OperB, otherwise coords are in OperA and the additional values is in OperB.
- if (textureInstructionSuffix != TextureInstructionSuffix.None && textureInstructionSuffix != TextureInstructionSuffix.Lz && textureTarget == GalTextureTarget.TwoD)
- {
- coords[coords.Length - coordStartIndex - 1] = opCode.Gpr8();
- coords[coords.Length - coordStartIndex - 1].Index += coords.Length - coordStartIndex - 1;
- operBIndex--;
- }
-
- // TODO: Find what MZ does and what changes about the encoding (Maybe Multisample?)
- if ((textureInstructionSuffix & TextureInstructionSuffix.Ll) != 0)
- {
- levelOfDetail = opCode.Gpr20();
- levelOfDetail.Index += operBIndex;
- operBIndex++;
- }
-
- if ((textureInstructionSuffix & TextureInstructionSuffix.AOffI) != 0)
- {
- offset = opCode.Gpr20();
- offset.Index += operBIndex;
- operBIndex++;
- }
-
- if ((textureInstructionSuffix & TextureInstructionSuffix.Dc) != 0)
- {
- depthCompare = opCode.Gpr20();
- depthCompare.Index += operBIndex;
- operBIndex++;
- }
-
- int lutIndex;
-
- lutIndex = !opCode.Gpr0().IsConst ? 1 : 0;
- lutIndex |= !opCode.Gpr28().IsConst ? 2 : 0;
-
- if (lutIndex == 0)
- {
- //Both destination registers are RZ, do nothing.
- return;
- }
-
- bool fp16 = !opCode.Read(59);
-
- int dstIncrement = 0;
-
- ShaderIrOperGpr GetDst()
- {
- ShaderIrOperGpr dst;
-
- if (fp16)
- {
- //FP16 mode, two components are packed on the two
- //halfs of a 32-bits register, as two half-float values.
- int halfPart = dstIncrement & 1;
-
- switch (lutIndex)
- {
- case 1: dst = opCode.GprHalf0(halfPart); break;
- case 2: dst = opCode.GprHalf28(halfPart); break;
- case 3: dst = (dstIncrement >> 1) != 0
- ? opCode.GprHalf28(halfPart)
- : opCode.GprHalf0(halfPart); break;
-
- default: throw new InvalidOperationException();
- }
- }
- else
- {
- //32-bits mode, each component uses one register.
- //Two components uses two consecutive registers.
- switch (lutIndex)
- {
- case 1: dst = opCode.Gpr0(); break;
- case 2: dst = opCode.Gpr28(); break;
- case 3: dst = (dstIncrement >> 1) != 0
- ? opCode.Gpr28()
- : opCode.Gpr0(); break;
-
- default: throw new InvalidOperationException();
- }
-
- dst.Index += dstIncrement & 1;
- }
-
- dstIncrement++;
-
- return dst;
- }
-
- int chMask = _maskLut[lutIndex, opCode.Read(50, 7)];
-
- if (chMask == 0)
- {
- //All channels are disabled, do nothing.
- return;
- }
-
- ShaderIrNode operC = opCode.Imm13_36();
- coords = CoordsRegistersToTempRegisters(block, coords);
-
- for (int ch = 0; ch < 4; ch++)
- {
- if (!IsChannelUsed(chMask, ch))
- {
- continue;
- }
-
- ShaderIrMetaTex meta = new ShaderIrMetaTex(ch, textureTarget, textureInstructionSuffix, coords)
- {
- LevelOfDetail = levelOfDetail,
- Offset = offset,
- DepthCompare = depthCompare
- };
- ShaderIrOp op = new ShaderIrOp(inst, operA, operB, operC, meta);
-
- ShaderIrOperGpr dst = GetDst();
-
- if (dst.IsValidRegister && !dst.IsConst)
- {
- block.AddNode(opCode.PredNode(new ShaderIrAsg(dst, op)));
- }
- }
- }
-
- private static void EmitTld4(ShaderIrBlock block, long opCode, GalTextureTarget textureType, TextureInstructionSuffix textureInstructionSuffix, int chMask, int component, bool scalar)
- {
- ShaderIrOperGpr operA = opCode.Gpr8();
- ShaderIrOperGpr operB = opCode.Gpr20();
- ShaderIrOperImm operC = opCode.Imm13_36();
-
- ShaderIrOperGpr[] coords = new ShaderIrOperGpr[ImageUtils.GetCoordsCountTextureTarget(textureType)];
-
- ShaderIrOperGpr offset = null;
- ShaderIrOperGpr depthCompare = null;
-
- bool isArray = ImageUtils.IsArray(textureType);
-
- int operBIndex = 0;
-
- if (scalar)
- {
- int coordStartIndex = 0;
-
- if (isArray)
- {
- coordStartIndex++;
- coords[coords.Length - 1] = operB;
- }
-
- switch (coords.Length - coordStartIndex)
- {
- case 1:
- coords[0] = opCode.Gpr8();
-
- break;
- case 2:
- coords[0] = opCode.Gpr8();
- coords[0].Index += coordStartIndex;
-
- break;
- case 3:
- coords[0] = opCode.Gpr8();
- coords[0].Index += coordStartIndex;
-
- coords[1] = opCode.Gpr8();
- coords[1].Index += 1 + coordStartIndex;
-
- break;
- default:
- throw new NotSupportedException($"{coords.Length - coordStartIndex} coords textures aren't supported in TLD4S");
- }
-
- if (coords.Length - coordStartIndex != 1)
- {
- coords[coords.Length - coordStartIndex - 1] = operB;
- operBIndex++;
- }
-
- if (textureInstructionSuffix != TextureInstructionSuffix.None && textureType == GalTextureTarget.TwoD)
- {
- coords[coords.Length - coordStartIndex - 1] = opCode.Gpr8();
- coords[coords.Length - coordStartIndex - 1].Index += coords.Length - coordStartIndex - 1;
- operBIndex--;
- }
- }
- else
- {
- int indexExtraCoord = 0;
-
- if (isArray)
- {
- indexExtraCoord++;
-
- coords[coords.Length - 1] = opCode.Gpr8();
- }
-
- for (int index = 0; index < coords.Length - indexExtraCoord; index++)
- {
- coords[index] = opCode.Gpr8();
-
- coords[index].Index += index;
-
- coords[index].Index += indexExtraCoord;
-
- if (coords[index].Index > ShaderIrOperGpr.ZrIndex)
- {
- coords[index].Index = ShaderIrOperGpr.ZrIndex;
- }
- }
- }
-
- if ((textureInstructionSuffix & TextureInstructionSuffix.AOffI) != 0)
- {
- offset = opCode.Gpr20();
- offset.Index += operBIndex;
- operBIndex++;
- }
-
- if ((textureInstructionSuffix & TextureInstructionSuffix.Dc) != 0)
- {
- depthCompare = opCode.Gpr20();
- depthCompare.Index += operBIndex;
- operBIndex++;
- }
-
- coords = CoordsRegistersToTempRegisters(block, coords);
-
- int regInc = 0;
-
- for (int ch = 0; ch < 4; ch++)
- {
- if (!IsChannelUsed(chMask, ch))
- {
- continue;
- }
-
- ShaderIrOperGpr dst = opCode.Gpr0();
-
- dst.Index += regInc++;
-
- if (!dst.IsValidRegister || dst.IsConst)
- {
- continue;
- }
-
- ShaderIrMetaTex meta = new ShaderIrMetaTex(ch, textureType, textureInstructionSuffix, coords)
- {
- Component = component,
- Offset = offset,
- DepthCompare = depthCompare
- };
-
- ShaderIrOp op = new ShaderIrOp(ShaderIrInst.Tld4, operA, operB, operC, meta);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(dst, op)));
- }
- }
-
- private static bool IsChannelUsed(int chMask, int ch)
- {
- return (chMask & (1 << ch)) != 0;
- }
-
- private static ShaderIrOperGpr[] CoordsRegistersToTempRegisters(ShaderIrBlock block, params ShaderIrOperGpr[] registers)
- {
- ShaderIrOperGpr[] res = new ShaderIrOperGpr[registers.Length];
-
- for (int index = 0; index < res.Length; index++)
- {
- res[index] = ShaderIrOperGpr.MakeTemporary(index);
- block.AddNode(new ShaderIrAsg(res[index], registers[index]));
- }
-
- return res;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
deleted file mode 100644
index 0a2b4232..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
+++ /dev/null
@@ -1,431 +0,0 @@
-using System;
-
-using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static partial class ShaderDecode
- {
- private enum IntType
- {
- U8 = 0,
- U16 = 1,
- U32 = 2,
- U64 = 3,
- S8 = 4,
- S16 = 5,
- S32 = 6,
- S64 = 7
- }
-
- private enum FloatType
- {
- F16 = 1,
- F32 = 2,
- F64 = 3
- }
-
- public static void F2f_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitF2F(block, opCode, ShaderOper.Cr);
- }
-
- public static void F2f_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitF2F(block, opCode, ShaderOper.Immf);
- }
-
- public static void F2f_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitF2F(block, opCode, ShaderOper.Rr);
- }
-
- public static void F2i_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitF2I(block, opCode, ShaderOper.Cr);
- }
-
- public static void F2i_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitF2I(block, opCode, ShaderOper.Immf);
- }
-
- public static void F2i_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitF2I(block, opCode, ShaderOper.Rr);
- }
-
- public static void I2f_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitI2F(block, opCode, ShaderOper.Cr);
- }
-
- public static void I2f_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitI2F(block, opCode, ShaderOper.Imm);
- }
-
- public static void I2f_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitI2F(block, opCode, ShaderOper.Rr);
- }
-
- public static void I2i_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitI2I(block, opCode, ShaderOper.Cr);
- }
-
- public static void I2i_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitI2I(block, opCode, ShaderOper.Imm);
- }
-
- public static void I2i_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitI2I(block, opCode, ShaderOper.Rr);
- }
-
- public static void Isberd(ShaderIrBlock block, long opCode, int position)
- {
- //This instruction seems to be used to translate from an address to a vertex index in a GS
- //Stub it as such
-
- block.AddNode(new ShaderIrCmnt("Stubbed."));
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), opCode.Gpr8())));
- }
-
- public static void Mov_C(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrOperCbuf cbuf = opCode.Cbuf34();
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), cbuf)));
- }
-
- public static void Mov_I(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrOperImm imm = opCode.Imm19_20();
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), imm)));
- }
-
- public static void Mov_I32(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrOperImm imm = opCode.Imm32_20();
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), imm)));
- }
-
- public static void Mov_R(ShaderIrBlock block, long opCode, int position)
- {
- ShaderIrOperGpr gpr = opCode.Gpr20();
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), gpr)));
- }
-
- public static void Sel_C(ShaderIrBlock block, long opCode, int position)
- {
- EmitSel(block, opCode, ShaderOper.Cr);
- }
-
- public static void Sel_I(ShaderIrBlock block, long opCode, int position)
- {
- EmitSel(block, opCode, ShaderOper.Imm);
- }
-
- public static void Sel_R(ShaderIrBlock block, long opCode, int position)
- {
- EmitSel(block, opCode, ShaderOper.Rr);
- }
-
- public static void Mov_S(ShaderIrBlock block, long opCode, int position)
- {
- block.AddNode(new ShaderIrCmnt("Stubbed."));
-
- //Zero is used as a special number to get a valid "0 * 0 + VertexIndex" in a GS
- ShaderIrNode source = new ShaderIrOperImm(0);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), source)));
- }
-
- private static void EmitF2F(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- bool negA = opCode.Read(45);
- bool absA = opCode.Read(49);
-
- ShaderIrNode operA;
-
- switch (oper)
- {
- case ShaderOper.Cr: operA = opCode.Cbuf34(); break;
- case ShaderOper.Immf: operA = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operA = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operA = GetAluFabsFneg(operA, absA, negA);
-
- ShaderIrInst roundInst = GetRoundInst(opCode);
-
- if (roundInst != ShaderIrInst.Invalid)
- {
- operA = new ShaderIrOp(roundInst, operA);
- }
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operA)));
- }
-
- private static void EmitF2I(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- IntType type = GetIntType(opCode);
-
- if (type == IntType.U64 ||
- type == IntType.S64)
- {
- //TODO: 64-bits support.
- //Note: GLSL doesn't support 64-bits integers.
- throw new NotImplementedException();
- }
-
- bool negA = opCode.Read(45);
- bool absA = opCode.Read(49);
-
- ShaderIrNode operA;
-
- switch (oper)
- {
- case ShaderOper.Cr: operA = opCode.Cbuf34(); break;
- case ShaderOper.Immf: operA = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operA = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operA = GetAluFabsFneg(operA, absA, negA);
-
- ShaderIrInst roundInst = GetRoundInst(opCode);
-
- if (roundInst != ShaderIrInst.Invalid)
- {
- operA = new ShaderIrOp(roundInst, operA);
- }
-
- bool signed = type >= IntType.S8;
-
- int size = 8 << ((int)type & 3);
-
- if (size < 32)
- {
- uint mask = uint.MaxValue >> (32 - size);
-
- float cMin = 0;
- float cMax = mask;
-
- if (signed)
- {
- uint halfMask = mask >> 1;
-
- cMin -= halfMask + 1;
- cMax = halfMask;
- }
-
- ShaderIrOperImmf min = new ShaderIrOperImmf(cMin);
- ShaderIrOperImmf max = new ShaderIrOperImmf(cMax);
-
- operA = new ShaderIrOp(ShaderIrInst.Fclamp, operA, min, max);
- }
-
- ShaderIrInst inst = signed
- ? ShaderIrInst.Ftos
- : ShaderIrInst.Ftou;
-
- ShaderIrNode op = new ShaderIrOp(inst, operA);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- private static void EmitI2F(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- IntType type = GetIntType(opCode);
-
- if (type == IntType.U64 ||
- type == IntType.S64)
- {
- //TODO: 64-bits support.
- //Note: GLSL doesn't support 64-bits integers.
- throw new NotImplementedException();
- }
-
- int sel = opCode.Read(41, 3);
-
- bool negA = opCode.Read(45);
- bool absA = opCode.Read(49);
-
- ShaderIrNode operA;
-
- switch (oper)
- {
- case ShaderOper.Cr: operA = opCode.Cbuf34(); break;
- case ShaderOper.Imm: operA = opCode.Imm19_20(); break;
- case ShaderOper.Rr: operA = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operA = GetAluIabsIneg(operA, absA, negA);
-
- bool signed = type >= IntType.S8;
-
- int shift = sel * 8;
-
- int size = 8 << ((int)type & 3);
-
- if (shift != 0)
- {
- operA = new ShaderIrOp(ShaderIrInst.Asr, operA, new ShaderIrOperImm(shift));
- }
-
- if (size < 32)
- {
- operA = ExtendTo32(operA, signed, size);
- }
-
- ShaderIrInst inst = signed
- ? ShaderIrInst.Stof
- : ShaderIrInst.Utof;
-
- ShaderIrNode op = new ShaderIrOp(inst, operA);
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), op)));
- }
-
- private static void EmitI2I(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- IntType type = GetIntType(opCode);
-
- if (type == IntType.U64 ||
- type == IntType.S64)
- {
- //TODO: 64-bits support.
- //Note: GLSL doesn't support 64-bits integers.
- throw new NotImplementedException();
- }
-
- int sel = opCode.Read(41, 3);
-
- bool negA = opCode.Read(45);
- bool absA = opCode.Read(49);
- bool satA = opCode.Read(50);
-
- ShaderIrNode operA;
-
- switch (oper)
- {
- case ShaderOper.Cr: operA = opCode.Cbuf34(); break;
- case ShaderOper.Immf: operA = opCode.Immf19_20(); break;
- case ShaderOper.Rr: operA = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- operA = GetAluIabsIneg(operA, absA, negA);
-
- bool signed = type >= IntType.S8;
-
- int shift = sel * 8;
-
- int size = 8 << ((int)type & 3);
-
- if (shift != 0)
- {
- operA = new ShaderIrOp(ShaderIrInst.Asr, operA, new ShaderIrOperImm(shift));
- }
-
- if (size < 32)
- {
- uint mask = uint.MaxValue >> (32 - size);
-
- if (satA)
- {
- uint cMin = 0;
- uint cMax = mask;
-
- if (signed)
- {
- uint halfMask = mask >> 1;
-
- cMin -= halfMask + 1;
- cMax = halfMask;
- }
-
- ShaderIrOperImm min = new ShaderIrOperImm((int)cMin);
- ShaderIrOperImm max = new ShaderIrOperImm((int)cMax);
-
- operA = new ShaderIrOp(signed
- ? ShaderIrInst.Clamps
- : ShaderIrInst.Clampu, operA, min, max);
- }
- else
- {
- operA = ExtendTo32(operA, signed, size);
- }
- }
-
- block.AddNode(opCode.PredNode(new ShaderIrAsg(opCode.Gpr0(), operA)));
- }
-
- private static void EmitSel(ShaderIrBlock block, long opCode, ShaderOper oper)
- {
- ShaderIrOperGpr dst = opCode.Gpr0();
- ShaderIrNode pred = opCode.Pred39N();
-
- ShaderIrNode resultA = opCode.Gpr8();
- ShaderIrNode resultB;
-
- switch (oper)
- {
- case ShaderOper.Cr: resultB = opCode.Cbuf34(); break;
- case ShaderOper.Imm: resultB = opCode.Imm19_20(); break;
- case ShaderOper.Rr: resultB = opCode.Gpr20(); break;
-
- default: throw new ArgumentException(nameof(oper));
- }
-
- block.AddNode(opCode.PredNode(new ShaderIrCond(pred, new ShaderIrAsg(dst, resultA), false)));
-
- block.AddNode(opCode.PredNode(new ShaderIrCond(pred, new ShaderIrAsg(dst, resultB), true)));
- }
-
- private static IntType GetIntType(long opCode)
- {
- bool signed = opCode.Read(13);
-
- IntType type = (IntType)(opCode.Read(10, 3));
-
- if (signed)
- {
- type += (int)IntType.S8;
- }
-
- return type;
- }
-
- private static FloatType GetFloatType(long opCode)
- {
- return (FloatType)(opCode.Read(8, 3));
- }
-
- private static ShaderIrInst GetRoundInst(long opCode)
- {
- switch (opCode.Read(39, 3))
- {
- case 1: return ShaderIrInst.Floor;
- case 2: return ShaderIrInst.Ceil;
- case 3: return ShaderIrInst.Trunc;
- }
-
- return ShaderIrInst.Invalid;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs
deleted file mode 100644
index 4b1e4046..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeOpCode.cs
+++ /dev/null
@@ -1,313 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static partial class ShaderDecode
- {
- private static int Read(this long opCode, int position, int mask)
- {
- return (int)(opCode >> position) & mask;
- }
-
- private static bool Read(this long opCode, int position)
- {
- return ((opCode >> position) & 1) != 0;
- }
-
- private static int Branch(this long opCode)
- {
- return ((int)(opCode >> 20) << 8) >> 8;
- }
-
- private static bool HasArray(this long opCode)
- {
- return opCode.Read(0x1c);
- }
-
- private static ShaderIrOperAbuf[] Abuf20(this long opCode)
- {
- int abuf = opCode.Read(20, 0x3ff);
- int size = opCode.Read(47, 3);
-
- ShaderIrOperGpr vertex = opCode.Gpr39();
-
- ShaderIrOperAbuf[] opers = new ShaderIrOperAbuf[size + 1];
-
- for (int index = 0; index <= size; index++)
- {
- opers[index] = new ShaderIrOperAbuf(abuf + index * 4, vertex);
- }
-
- return opers;
- }
-
- private static ShaderIrOperAbuf Abuf28(this long opCode)
- {
- int abuf = opCode.Read(28, 0x3ff);
-
- return new ShaderIrOperAbuf(abuf, opCode.Gpr39());
- }
-
- private static ShaderIrOperCbuf Cbuf34(this long opCode)
- {
- return new ShaderIrOperCbuf(
- opCode.Read(34, 0x1f),
- opCode.Read(20, 0x3fff));
- }
-
- private static ShaderIrOperGpr Gpr8(this long opCode)
- {
- return new ShaderIrOperGpr(opCode.Read(8, 0xff));
- }
-
- private static ShaderIrOperGpr Gpr20(this long opCode)
- {
- return new ShaderIrOperGpr(opCode.Read(20, 0xff));
- }
-
- private static ShaderIrOperGpr Gpr39(this long opCode)
- {
- return new ShaderIrOperGpr(opCode.Read(39, 0xff));
- }
-
- private static ShaderIrOperGpr Gpr0(this long opCode)
- {
- return new ShaderIrOperGpr(opCode.Read(0, 0xff));
- }
-
- private static ShaderIrOperGpr Gpr28(this long opCode)
- {
- return new ShaderIrOperGpr(opCode.Read(28, 0xff));
- }
-
- private static ShaderIrOperGpr[] GprHalfVec8(this long opCode)
- {
- return GetGprHalfVec2(opCode.Read(8, 0xff), opCode.Read(47, 3));
- }
-
- private static ShaderIrOperGpr[] GprHalfVec20(this long opCode)
- {
- return GetGprHalfVec2(opCode.Read(20, 0xff), opCode.Read(28, 3));
- }
-
- private static ShaderIrOperGpr[] GetGprHalfVec2(int gpr, int mask)
- {
- if (mask == 1)
- {
- //This value is used for FP32, the whole 32-bits register
- //is used as each element on the vector.
- return new ShaderIrOperGpr[]
- {
- new ShaderIrOperGpr(gpr),
- new ShaderIrOperGpr(gpr)
- };
- }
-
- ShaderIrOperGpr low = new ShaderIrOperGpr(gpr, 0);
- ShaderIrOperGpr high = new ShaderIrOperGpr(gpr, 1);
-
- return new ShaderIrOperGpr[]
- {
- (mask & 1) != 0 ? high : low,
- (mask & 2) != 0 ? high : low
- };
- }
-
- private static ShaderIrOperGpr GprHalf0(this long opCode, int halfPart)
- {
- return new ShaderIrOperGpr(opCode.Read(0, 0xff), halfPart);
- }
-
- private static ShaderIrOperGpr GprHalf28(this long opCode, int halfPart)
- {
- return new ShaderIrOperGpr(opCode.Read(28, 0xff), halfPart);
- }
-
- private static ShaderIrOperImm Imm5_39(this long opCode)
- {
- return new ShaderIrOperImm(opCode.Read(39, 0x1f));
- }
-
- private static ShaderIrOperImm Imm13_36(this long opCode)
- {
- return new ShaderIrOperImm(opCode.Read(36, 0x1fff));
- }
-
- private static ShaderIrOperImm Imm32_20(this long opCode)
- {
- return new ShaderIrOperImm((int)(opCode >> 20));
- }
-
- private static ShaderIrOperImmf Immf32_20(this long opCode)
- {
- return new ShaderIrOperImmf(BitConverter.Int32BitsToSingle((int)(opCode >> 20)));
- }
-
- private static ShaderIrOperImm ImmU16_20(this long opCode)
- {
- return new ShaderIrOperImm(opCode.Read(20, 0xffff));
- }
-
- private static ShaderIrOperImm Imm19_20(this long opCode)
- {
- int value = opCode.Read(20, 0x7ffff);
-
- bool neg = opCode.Read(56);
-
- if (neg)
- {
- value = -value;
- }
-
- return new ShaderIrOperImm(value);
- }
-
- private static ShaderIrOperImmf Immf19_20(this long opCode)
- {
- uint imm = (uint)(opCode >> 20) & 0x7ffff;
-
- bool neg = opCode.Read(56);
-
- imm <<= 12;
-
- if (neg)
- {
- imm |= 0x80000000;
- }
-
- float value = BitConverter.Int32BitsToSingle((int)imm);
-
- return new ShaderIrOperImmf(value);
- }
-
- private static ShaderIrOperPred Pred0(this long opCode)
- {
- return new ShaderIrOperPred(opCode.Read(0, 7));
- }
-
- private static ShaderIrOperPred Pred3(this long opCode)
- {
- return new ShaderIrOperPred(opCode.Read(3, 7));
- }
-
- private static ShaderIrOperPred Pred12(this long opCode)
- {
- return new ShaderIrOperPred(opCode.Read(12, 7));
- }
-
- private static ShaderIrOperPred Pred29(this long opCode)
- {
- return new ShaderIrOperPred(opCode.Read(29, 7));
- }
-
- private static ShaderIrNode Pred39N(this long opCode)
- {
- ShaderIrNode node = opCode.Pred39();
-
- if (opCode.Read(42))
- {
- node = new ShaderIrOp(ShaderIrInst.Bnot, node);
- }
-
- return node;
- }
-
- private static ShaderIrOperPred Pred39(this long opCode)
- {
- return new ShaderIrOperPred(opCode.Read(39, 7));
- }
-
- private static ShaderIrOperPred Pred48(this long opCode)
- {
- return new ShaderIrOperPred(opCode.Read(48, 7));
- }
-
- private static ShaderIrInst Cmp(this long opCode)
- {
- switch (opCode.Read(49, 7))
- {
- case 1: return ShaderIrInst.Clt;
- case 2: return ShaderIrInst.Ceq;
- case 3: return ShaderIrInst.Cle;
- case 4: return ShaderIrInst.Cgt;
- case 5: return ShaderIrInst.Cne;
- case 6: return ShaderIrInst.Cge;
- }
-
- throw new ArgumentException(nameof(opCode));
- }
-
- private static ShaderIrInst CmpF(this long opCode)
- {
- switch (opCode.Read(48, 0xf))
- {
- case 0x1: return ShaderIrInst.Fclt;
- case 0x2: return ShaderIrInst.Fceq;
- case 0x3: return ShaderIrInst.Fcle;
- case 0x4: return ShaderIrInst.Fcgt;
- case 0x5: return ShaderIrInst.Fcne;
- case 0x6: return ShaderIrInst.Fcge;
- case 0x7: return ShaderIrInst.Fcnum;
- case 0x8: return ShaderIrInst.Fcnan;
- case 0x9: return ShaderIrInst.Fcltu;
- case 0xa: return ShaderIrInst.Fcequ;
- case 0xb: return ShaderIrInst.Fcleu;
- case 0xc: return ShaderIrInst.Fcgtu;
- case 0xd: return ShaderIrInst.Fcneu;
- case 0xe: return ShaderIrInst.Fcgeu;
- }
-
- throw new ArgumentException(nameof(opCode));
- }
-
- private static ShaderIrInst BLop45(this long opCode)
- {
- switch (opCode.Read(45, 3))
- {
- case 0: return ShaderIrInst.Band;
- case 1: return ShaderIrInst.Bor;
- case 2: return ShaderIrInst.Bxor;
- }
-
- throw new ArgumentException(nameof(opCode));
- }
-
- private static ShaderIrInst BLop24(this long opCode)
- {
- switch (opCode.Read(24, 3))
- {
- case 0: return ShaderIrInst.Band;
- case 1: return ShaderIrInst.Bor;
- case 2: return ShaderIrInst.Bxor;
- }
-
- throw new ArgumentException(nameof(opCode));
- }
-
- private static ShaderIrNode PredNode(this long opCode, ShaderIrNode node)
- {
- ShaderIrOperPred pred = opCode.PredNode();
-
- if (pred.Index != ShaderIrOperPred.UnusedIndex)
- {
- bool inv = opCode.Read(19);
-
- node = new ShaderIrCond(pred, node, inv);
- }
-
- return node;
- }
-
- private static ShaderIrOperPred PredNode(this long opCode)
- {
- int pred = opCode.Read(16, 0xf);
-
- if (pred != 0xf)
- {
- pred &= 7;
- }
-
- return new ShaderIrOperPred(pred);
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs
deleted file mode 100644
index 9098ca5e..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static partial class ShaderDecode
- {
- public static void Out_R(ShaderIrBlock block, long opCode, int position)
- {
- //TODO: Those registers have to be used for something
- ShaderIrOperGpr gpr0 = opCode.Gpr0();
- ShaderIrOperGpr gpr8 = opCode.Gpr8();
- ShaderIrOperGpr gpr20 = opCode.Gpr20();
-
- int type = opCode.Read(39, 3);
-
- if ((type & 1) != 0)
- {
- block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Emit)));
- }
-
- if ((type & 2) != 0)
- {
- block.AddNode(opCode.PredNode(new ShaderIrOp(ShaderIrInst.Cut)));
- }
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
deleted file mode 100644
index 4b23f8d0..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static class ShaderDecoder
- {
- private const long HeaderSize = 0x50;
-
- private const bool AddDbgComments = true;
-
- public static ShaderIrBlock[] Decode(IGalMemory memory, long start)
- {
- Dictionary<int, ShaderIrBlock> visited = new Dictionary<int, ShaderIrBlock>();
- Dictionary<int, ShaderIrBlock> visitedEnd = new Dictionary<int, ShaderIrBlock>();
-
- Queue<ShaderIrBlock> blocks = new Queue<ShaderIrBlock>();
-
- long beginning = start + HeaderSize;
-
- ShaderIrBlock Enqueue(int position, ShaderIrBlock source = null)
- {
- if (!visited.TryGetValue(position, out ShaderIrBlock output))
- {
- output = new ShaderIrBlock(position);
-
- blocks.Enqueue(output);
-
- visited.Add(position, output);
- }
-
- if (source != null)
- {
- output.Sources.Add(source);
- }
-
- return output;
- }
-
- ShaderIrBlock entry = Enqueue(0);
-
- while (blocks.Count > 0)
- {
- ShaderIrBlock current = blocks.Dequeue();
-
- FillBlock(memory, current, beginning);
-
- //Set child blocks. "Branch" is the block the branch instruction
- //points to (when taken), "Next" is the block at the next address,
- //executed when the branch is not taken. For Unconditional Branches
- //or end of shader, Next is null.
- if (current.Nodes.Count > 0)
- {
- ShaderIrNode lastNode = current.GetLastNode();
-
- ShaderIrOp innerOp = GetInnermostOp(lastNode);
-
- if (innerOp?.Inst == ShaderIrInst.Bra)
- {
- int target = ((ShaderIrOperImm)innerOp.OperandA).Value;
-
- current.Branch = Enqueue(target, current);
- }
-
- foreach (ShaderIrNode node in current.Nodes)
- {
- innerOp = GetInnermostOp(node);
-
- if (innerOp is ShaderIrOp currOp && currOp.Inst == ShaderIrInst.Ssy)
- {
- int target = ((ShaderIrOperImm)currOp.OperandA).Value;
-
- Enqueue(target, current);
- }
- }
-
- if (NodeHasNext(lastNode))
- {
- current.Next = Enqueue(current.EndPosition);
- }
- }
-
- //If we have on the graph two blocks with the same end position,
- //then we need to split the bigger block and have two small blocks,
- //the end position of the bigger "Current" block should then be == to
- //the position of the "Smaller" block.
- while (visitedEnd.TryGetValue(current.EndPosition, out ShaderIrBlock smaller))
- {
- if (current.Position > smaller.Position)
- {
- ShaderIrBlock temp = smaller;
-
- smaller = current;
- current = temp;
- }
-
- current.EndPosition = smaller.Position;
- current.Next = smaller;
- current.Branch = null;
-
- current.Nodes.RemoveRange(
- current.Nodes.Count - smaller.Nodes.Count,
- smaller.Nodes.Count);
-
- visitedEnd[smaller.EndPosition] = smaller;
- }
-
- visitedEnd.Add(current.EndPosition, current);
- }
-
- //Make and sort Graph blocks array by position.
- ShaderIrBlock[] graph = new ShaderIrBlock[visited.Count];
-
- while (visited.Count > 0)
- {
- uint firstPos = uint.MaxValue;
-
- foreach (ShaderIrBlock block in visited.Values)
- {
- if (firstPos > (uint)block.Position)
- firstPos = (uint)block.Position;
- }
-
- ShaderIrBlock current = visited[(int)firstPos];
-
- do
- {
- graph[graph.Length - visited.Count] = current;
-
- visited.Remove(current.Position);
-
- current = current.Next;
- }
- while (current != null);
- }
-
- return graph;
- }
-
- private static void FillBlock(IGalMemory memory, ShaderIrBlock block, long beginning)
- {
- int position = block.Position;
-
- do
- {
- //Ignore scheduling instructions, which are written every 32 bytes.
- if ((position & 0x1f) == 0)
- {
- position += 8;
-
- continue;
- }
-
- uint word0 = (uint)memory.ReadInt32(position + beginning + 0);
- uint word1 = (uint)memory.ReadInt32(position + beginning + 4);
-
- position += 8;
-
- long opCode = word0 | (long)word1 << 32;
-
- ShaderDecodeFunc decode = ShaderOpCodeTable.GetDecoder(opCode);
-
- if (AddDbgComments)
- {
- string dbgOpCode = $"0x{(position - 8):x16}: 0x{opCode:x16} ";
-
- dbgOpCode += (decode?.Method.Name ?? "???");
-
- if (decode == ShaderDecode.Bra || decode == ShaderDecode.Ssy)
- {
- int offset = ((int)(opCode >> 20) << 8) >> 8;
-
- long target = position + offset;
-
- dbgOpCode += " (0x" + target.ToString("x16") + ")";
- }
-
- block.AddNode(new ShaderIrCmnt(dbgOpCode));
- }
-
- if (decode == null)
- {
- continue;
- }
-
- decode(block, opCode, position);
- }
- while (!IsFlowChange(block.GetLastNode()));
-
- block.EndPosition = position;
- }
-
- private static bool IsFlowChange(ShaderIrNode node)
- {
- return !NodeHasNext(GetInnermostOp(node));
- }
-
- private static ShaderIrOp GetInnermostOp(ShaderIrNode node)
- {
- if (node is ShaderIrCond cond)
- {
- node = cond.Child;
- }
-
- return node is ShaderIrOp op ? op : null;
- }
-
- private static bool NodeHasNext(ShaderIrNode node)
- {
- if (!(node is ShaderIrOp op))
- {
- return true;
- }
-
- return op.Inst != ShaderIrInst.Exit &&
- op.Inst != ShaderIrInst.Bra;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs
deleted file mode 100644
index 2f9326e1..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- struct OmapTarget
- {
- public bool Red;
- public bool Green;
- public bool Blue;
- public bool Alpha;
-
- public bool Enabled => Red || Green || Blue || Alpha;
-
- public bool ComponentEnabled(int component)
- {
- switch (component)
- {
- case 0: return Red;
- case 1: return Green;
- case 2: return Blue;
- case 3: return Alpha;
- }
-
- throw new ArgumentException(nameof(component));
- }
- }
-
- class ShaderHeader
- {
- public const int PointList = 1;
- public const int LineStrip = 6;
- public const int TriangleStrip = 7;
-
- public int SphType { get; private set; }
- public int Version { get; private set; }
- public int ShaderType { get; private set; }
- public bool MrtEnable { get; private set; }
- public bool KillsPixels { get; private set; }
- public bool DoesGlobalStore { get; private set; }
- public int SassVersion { get; private set; }
- public bool DoesLoadOrStore { get; private set; }
- public bool DoesFp64 { get; private set; }
- public int StreamOutMask { get; private set; }
-
- public int ShaderLocalMemoryLowSize { get; private set; }
- public int PerPatchAttributeCount { get; private set; }
-
- public int ShaderLocalMemoryHighSize { get; private set; }
- public int ThreadsPerInputPrimitive { get; private set; }
-
- public int ShaderLocalMemoryCrsSize { get; private set; }
- public int OutputTopology { get; private set; }
-
- public int MaxOutputVertexCount { get; private set; }
- public int StoreReqStart { get; private set; }
- public int StoreReqEnd { get; private set; }
-
- public OmapTarget[] OmapTargets { get; private set; }
- public bool OmapSampleMask { get; private set; }
- public bool OmapDepth { get; private set; }
-
- public ShaderHeader(IGalMemory memory, long position)
- {
- uint commonWord0 = (uint)memory.ReadInt32(position + 0);
- uint commonWord1 = (uint)memory.ReadInt32(position + 4);
- uint commonWord2 = (uint)memory.ReadInt32(position + 8);
- uint commonWord3 = (uint)memory.ReadInt32(position + 12);
- uint commonWord4 = (uint)memory.ReadInt32(position + 16);
-
- SphType = ReadBits(commonWord0, 0, 5);
- Version = ReadBits(commonWord0, 5, 5);
- ShaderType = ReadBits(commonWord0, 10, 4);
- MrtEnable = ReadBits(commonWord0, 14, 1) != 0;
- KillsPixels = ReadBits(commonWord0, 15, 1) != 0;
- DoesGlobalStore = ReadBits(commonWord0, 16, 1) != 0;
- SassVersion = ReadBits(commonWord0, 17, 4);
- DoesLoadOrStore = ReadBits(commonWord0, 26, 1) != 0;
- DoesFp64 = ReadBits(commonWord0, 27, 1) != 0;
- StreamOutMask = ReadBits(commonWord0, 28, 4);
-
- ShaderLocalMemoryLowSize = ReadBits(commonWord1, 0, 24);
- PerPatchAttributeCount = ReadBits(commonWord1, 24, 8);
-
- ShaderLocalMemoryHighSize = ReadBits(commonWord2, 0, 24);
- ThreadsPerInputPrimitive = ReadBits(commonWord2, 24, 8);
-
- ShaderLocalMemoryCrsSize = ReadBits(commonWord3, 0, 24);
- OutputTopology = ReadBits(commonWord3, 24, 4);
-
- MaxOutputVertexCount = ReadBits(commonWord4, 0, 12);
- StoreReqStart = ReadBits(commonWord4, 12, 8);
- StoreReqEnd = ReadBits(commonWord4, 24, 8);
-
- //Type 2 (fragment?) reading
- uint type2OmapTarget = (uint)memory.ReadInt32(position + 72);
- uint type2Omap = (uint)memory.ReadInt32(position + 76);
-
- OmapTargets = new OmapTarget[8];
-
- for (int i = 0; i < OmapTargets.Length; i++)
- {
- int offset = i * 4;
-
- OmapTargets[i] = new OmapTarget
- {
- Red = ReadBits(type2OmapTarget, offset + 0, 1) != 0,
- Green = ReadBits(type2OmapTarget, offset + 1, 1) != 0,
- Blue = ReadBits(type2OmapTarget, offset + 2, 1) != 0,
- Alpha = ReadBits(type2OmapTarget, offset + 3, 1) != 0
- };
- }
-
- OmapSampleMask = ReadBits(type2Omap, 0, 1) != 0;
- OmapDepth = ReadBits(type2Omap, 1, 1) != 0;
- }
-
- public int DepthRegister
- {
- get
- {
- int count = 0;
-
- for (int index = 0; index < OmapTargets.Length; index++)
- {
- for (int component = 0; component < 4; component++)
- {
- if (OmapTargets[index].ComponentEnabled(component))
- {
- count++;
- }
- }
- }
-
- // Depth register is always two registers after the last color output
- return count + 1;
- }
- }
-
- private static int ReadBits(uint word, int offset, int bitWidth)
- {
- uint mask = (1u << bitWidth) - 1u;
-
- return (int)((word >> offset) & mask);
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs
deleted file mode 100644
index b3713fa4..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- enum ShaderIpaMode
- {
- Pass = 0,
- None = 1,
- Constant = 2,
- Sc = 3
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs
deleted file mode 100644
index 53871a14..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrAsg : ShaderIrNode
- {
- public ShaderIrNode Dst { get; set; }
- public ShaderIrNode Src { get; set; }
-
- public ShaderIrAsg(ShaderIrNode dst, ShaderIrNode src)
- {
- Dst = dst;
- Src = src;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
deleted file mode 100644
index 49257d28..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrBlock
- {
- public int Position { get; set; }
- public int EndPosition { get; set; }
-
- public ShaderIrBlock Next { get; set; }
- public ShaderIrBlock Branch { get; set; }
-
- public List<ShaderIrBlock> Sources { get; private set; }
-
- public List<ShaderIrNode> Nodes { get; private set; }
-
- public ShaderIrBlock(int position)
- {
- Position = position;
-
- Sources = new List<ShaderIrBlock>();
-
- Nodes = new List<ShaderIrNode>();
- }
-
- public void AddNode(ShaderIrNode node)
- {
- Nodes.Add(node);
- }
-
- public ShaderIrNode[] GetNodes()
- {
- return Nodes.ToArray();
- }
-
- public ShaderIrNode GetLastNode()
- {
- if (Nodes.Count > 0)
- {
- return Nodes[Nodes.Count - 1];
- }
-
- return null;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs
deleted file mode 100644
index 5da04e5e..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrCmnt : ShaderIrNode
- {
- public string Comment { get; private set; }
-
- public ShaderIrCmnt(string comment)
- {
- Comment = comment;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs
deleted file mode 100644
index 34acf90d..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrCond : ShaderIrNode
- {
- public ShaderIrNode Pred { get; set; }
- public ShaderIrNode Child { get; set; }
-
- public bool Not { get; private set; }
-
- public ShaderIrCond(ShaderIrNode pred, ShaderIrNode child, bool not)
- {
- Pred = pred;
- Child = child;
- Not = not;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
deleted file mode 100644
index 68ff214e..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- enum ShaderIrInst
- {
- Invalid,
-
- B_Start,
- Band,
- Bnot,
- Bor,
- Bxor,
- B_End,
-
- F_Start,
- Ceil,
-
- Fabs,
- Fadd,
- Fceq,
- Fcequ,
- Fcge,
- Fcgeu,
- Fcgt,
- Fcgtu,
- Fclamp,
- Fcle,
- Fcleu,
- Fclt,
- Fcltu,
- Fcnan,
- Fcne,
- Fcneu,
- Fcnum,
- Fcos,
- Fex2,
- Ffma,
- Flg2,
- Floor,
- Fmax,
- Fmin,
- Fmul,
- Fneg,
- Frcp,
- Frsq,
- Fsin,
- Fsqrt,
- Ftos,
- Ftou,
- Ipa,
- Texb,
- Texs,
- Tld4,
- Trunc,
- F_End,
-
- I_Start,
- Abs,
- Add,
- And,
- Asr,
- Ceq,
- Cge,
- Cgt,
- Clamps,
- Clampu,
- Cle,
- Clt,
- Cne,
- Lsl,
- Lsr,
- Max,
- Min,
- Mul,
- Neg,
- Not,
- Or,
- Stof,
- Sub,
- Texq,
- Txlf,
- Utof,
- Xor,
- I_End,
-
- Bra,
- Exit,
- Kil,
- Ssy,
- Sync,
-
- Emit,
- Cut
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs
deleted file mode 100644
index afb7503b..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrMeta { }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs
deleted file mode 100644
index 07db6467..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrMetaIpa : ShaderIrMeta
- {
- public ShaderIpaMode Mode { get; private set; }
-
- public ShaderIrMetaIpa(ShaderIpaMode mode)
- {
- Mode = mode;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs
deleted file mode 100644
index e0265138..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Ryujinx.Graphics.Texture;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrMetaTex : ShaderIrMeta
- {
- public int Elem { get; private set; }
- public GalTextureTarget TextureTarget { get; private set; }
- public ShaderIrNode[] Coordinates { get; private set; }
- public TextureInstructionSuffix TextureInstructionSuffix { get; private set; }
- public ShaderIrOperGpr LevelOfDetail;
- public ShaderIrOperGpr Offset;
- public ShaderIrOperGpr DepthCompare;
- public int Component; // for TLD4(S)
-
- public ShaderIrMetaTex(int elem, GalTextureTarget textureTarget, TextureInstructionSuffix textureInstructionSuffix, params ShaderIrNode[] coordinates)
- {
- Elem = elem;
- TextureTarget = textureTarget;
- TextureInstructionSuffix = textureInstructionSuffix;
- Coordinates = coordinates;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs
deleted file mode 100644
index c925ea4e..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrMetaTexq : ShaderIrMeta
- {
- public ShaderTexqInfo Info { get; private set; }
-
- public int Elem { get; private set; }
-
- public ShaderIrMetaTexq(ShaderTexqInfo info, int elem)
- {
- Info = info;
- Elem = elem;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs
deleted file mode 100644
index 2648164a..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrNode { }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs
deleted file mode 100644
index c91c3926..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrOp : ShaderIrNode
- {
- public ShaderIrInst Inst { get; private set; }
- public ShaderIrNode OperandA { get; set; }
- public ShaderIrNode OperandB { get; set; }
- public ShaderIrNode OperandC { get; set; }
- public ShaderIrMeta MetaData { get; set; }
-
- public ShaderIrOp(
- ShaderIrInst inst,
- ShaderIrNode operandA = null,
- ShaderIrNode operandB = null,
- ShaderIrNode operandC = null,
- ShaderIrMeta metaData = null)
- {
- Inst = inst;
- OperandA = operandA;
- OperandB = operandB;
- OperandC = operandC;
- MetaData = metaData;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs
deleted file mode 100644
index 1f339e80..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrOperAbuf : ShaderIrNode
- {
- public int Offs { get; private set; }
-
- public ShaderIrNode Vertex { get; private set; }
-
- public ShaderIrOperAbuf(int offs, ShaderIrNode vertex)
- {
- Offs = offs;
- Vertex = vertex;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs
deleted file mode 100644
index 9f419bbb..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrOperCbuf : ShaderIrNode
- {
- public int Index { get; private set; }
- public int Pos { get; set; }
-
- public ShaderIrNode Offs { get; private set; }
-
- public ShaderIrOperCbuf(int index, int pos, ShaderIrNode offs = null)
- {
- Index = index;
- Pos = pos;
- Offs = offs;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs
deleted file mode 100644
index 0d102d89..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrOperGpr : ShaderIrNode
- {
- public const int ZrIndex = 0xff;
-
- public bool IsConst => Index == ZrIndex;
-
- public bool IsValidRegister => (uint)Index <= ZrIndex;
-
- public int Index { get; set; }
- public int HalfPart { get; set; }
-
- public ShaderRegisterSize RegisterSize { get; private set; }
-
- public ShaderIrOperGpr(int index)
- {
- Index = index;
-
- RegisterSize = ShaderRegisterSize.Single;
- }
-
- public ShaderIrOperGpr(int index, int halfPart)
- {
- Index = index;
- HalfPart = halfPart;
-
- RegisterSize = ShaderRegisterSize.Half;
- }
-
- public static ShaderIrOperGpr MakeTemporary(int index = 0)
- {
- return new ShaderIrOperGpr(0x100 + index);
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs
deleted file mode 100644
index 6b23b365..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrOperImm : ShaderIrNode
- {
- public int Value { get; private set; }
-
- public ShaderIrOperImm(int value)
- {
- Value = value;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs
deleted file mode 100644
index 5b08c5b1..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrOperImmf : ShaderIrNode
- {
- public float Value { get; private set; }
-
- public ShaderIrOperImmf(float value)
- {
- Value = value;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs
deleted file mode 100644
index 6c16a145..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- class ShaderIrOperPred : ShaderIrNode
- {
- public const int UnusedIndex = 0x7;
- public const int NeverExecute = 0xf;
-
- public bool IsConst => Index >= UnusedIndex;
-
- public int Index { get; set; }
-
- public ShaderIrOperPred(int index)
- {
- Index = index;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
deleted file mode 100644
index 1edf91a0..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Gal.Shader
-{
- static class ShaderOpCodeTable
- {
- private const int EncodingBits = 14;
-
- private class ShaderDecodeEntry
- {
- public ShaderDecodeFunc Func;
-
- public int XBits;
-
- public ShaderDecodeEntry(ShaderDecodeFunc func, int xBits)
- {
- Func = func;
- XBits = xBits;
- }
- }
-
- private static ShaderDecodeEntry[] _opCodes;
-
- static ShaderOpCodeTable()
- {
- _opCodes = new ShaderDecodeEntry[1 << EncodingBits];
-
-#region Instructions
- Set("0100110000000x", ShaderDecode.Bfe_C);
- Set("0011100x00000x", ShaderDecode.Bfe_I);
- Set("0101110000000x", ShaderDecode.Bfe_R);
- Set("111000100100xx", ShaderDecode.Bra);
- Set("111000110000xx", ShaderDecode.Exit);
- Set("0100110010101x", ShaderDecode.F2f_C);
- Set("0011100x10101x", ShaderDecode.F2f_I);
- Set("0101110010101x", ShaderDecode.F2f_R);
- Set("0100110010110x", ShaderDecode.F2i_C);
- Set("0011100x10110x", ShaderDecode.F2i_I);
- Set("0101110010110x", ShaderDecode.F2i_R);
- Set("0100110001011x", ShaderDecode.Fadd_C);
- Set("0011100x01011x", ShaderDecode.Fadd_I);
- Set("000010xxxxxxxx", ShaderDecode.Fadd_I32);
- Set("0101110001011x", ShaderDecode.Fadd_R);
- Set("010010011xxxxx", ShaderDecode.Ffma_CR);
- Set("0011001x1xxxxx", ShaderDecode.Ffma_I);
- Set("010100011xxxxx", ShaderDecode.Ffma_RC);
- Set("010110011xxxxx", ShaderDecode.Ffma_RR);
- Set("0100110001101x", ShaderDecode.Fmul_C);
- Set("0011100x01101x", ShaderDecode.Fmul_I);
- Set("00011110xxxxxx", ShaderDecode.Fmul_I32);
- Set("0101110001101x", ShaderDecode.Fmul_R);
- Set("0100110001100x", ShaderDecode.Fmnmx_C);
- Set("0011100x01100x", ShaderDecode.Fmnmx_I);
- Set("0101110001100x", ShaderDecode.Fmnmx_R);
- Set("0100100xxxxxxx", ShaderDecode.Fset_C);
- Set("0011000xxxxxxx", ShaderDecode.Fset_I);
- Set("01011000xxxxxx", ShaderDecode.Fset_R);
- Set("010010111011xx", ShaderDecode.Fsetp_C);
- Set("0011011x1011xx", ShaderDecode.Fsetp_I);
- Set("010110111011xx", ShaderDecode.Fsetp_R);
- Set("0101110100010x", ShaderDecode.Hadd2_R);
- Set("0101110100001x", ShaderDecode.Hmul2_R);
- Set("0100110010111x", ShaderDecode.I2f_C);
- Set("0011100x10111x", ShaderDecode.I2f_I);
- Set("0101110010111x", ShaderDecode.I2f_R);
- Set("0100110011100x", ShaderDecode.I2i_C);
- Set("0011100x11100x", ShaderDecode.I2i_I);
- Set("0101110011100x", ShaderDecode.I2i_R);
- Set("0100110000010x", ShaderDecode.Iadd_C);
- Set("0011100000010x", ShaderDecode.Iadd_I);
- Set("0001110x0xxxxx", ShaderDecode.Iadd_I32);
- Set("0101110000010x", ShaderDecode.Iadd_R);
- Set("010011001100xx", ShaderDecode.Iadd3_C);
- Set("001110001100xx", ShaderDecode.Iadd3_I);
- Set("010111001100xx", ShaderDecode.Iadd3_R);
- Set("0100110000100x", ShaderDecode.Imnmx_C);
- Set("0011100x00100x", ShaderDecode.Imnmx_I);
- Set("0101110000100x", ShaderDecode.Imnmx_R);
- Set("1110111111010x", ShaderDecode.Isberd);
- Set("11100000xxxxxx", ShaderDecode.Ipa);
- Set("0100110000011x", ShaderDecode.Iscadd_C);
- Set("0011100x00011x", ShaderDecode.Iscadd_I);
- Set("0101110000011x", ShaderDecode.Iscadd_R);
- Set("010010110101xx", ShaderDecode.Iset_C);
- Set("001101100101xx", ShaderDecode.Iset_I);
- Set("010110110101xx", ShaderDecode.Iset_R);
- Set("010010110110xx", ShaderDecode.Isetp_C);
- Set("0011011x0110xx", ShaderDecode.Isetp_I);
- Set("010110110110xx", ShaderDecode.Isetp_R);
- Set("111000110011xx", ShaderDecode.Kil);
- Set("1110111111011x", ShaderDecode.Ld_A);
- Set("1110111110010x", ShaderDecode.Ld_C);
- Set("0100110001000x", ShaderDecode.Lop_C);
- Set("0011100001000x", ShaderDecode.Lop_I);
- Set("000001xxxxxxxx", ShaderDecode.Lop_I32);
- Set("0101110001000x", ShaderDecode.Lop_R);
- Set("0100110010011x", ShaderDecode.Mov_C);
- Set("0011100x10011x", ShaderDecode.Mov_I);
- Set("000000010000xx", ShaderDecode.Mov_I32);
- Set("0101110010011x", ShaderDecode.Mov_R);
- Set("1111000011001x", ShaderDecode.Mov_S);
- Set("0101000010000x", ShaderDecode.Mufu);
- Set("1111101111100x", ShaderDecode.Out_R);
- Set("0101000010010x", ShaderDecode.Psetp);
- Set("0100110010010x", ShaderDecode.Rro_C);
- Set("0011100x10010x", ShaderDecode.Rro_I);
- Set("0101110010010x", ShaderDecode.Rro_R);
- Set("0100110010100x", ShaderDecode.Sel_C);
- Set("0011100010100x", ShaderDecode.Sel_I);
- Set("0101110010100x", ShaderDecode.Sel_R);
- Set("0100110001001x", ShaderDecode.Shl_C);
- Set("0011100x01001x", ShaderDecode.Shl_I);
- Set("0101110001001x", ShaderDecode.Shl_R);
- Set("0100110000101x", ShaderDecode.Shr_C);
- Set("0011100x00101x", ShaderDecode.Shr_I);
- Set("0101110000101x", ShaderDecode.Shr_R);
- Set("111000101001xx", ShaderDecode.Ssy);
- Set("1110111111110x", ShaderDecode.St_A);
- Set("1111000011111x", ShaderDecode.Sync);
- Set("110000xxxx111x", ShaderDecode.Tex);
- Set("1101111010111x", ShaderDecode.Tex_B);
- Set("1101111101001x", ShaderDecode.Texq);
- Set("1101x00xxxxxxx", ShaderDecode.Texs);
- Set("1101101xxxxxxx", ShaderDecode.Tlds);
- Set("110010xxxx111x", ShaderDecode.Tld4);
- Set("1101111100xxxx", ShaderDecode.Tld4S);
- Set("01011111xxxxxx", ShaderDecode.Vmad);
- Set("0100111xxxxxxx", ShaderDecode.Xmad_CR);
- Set("0011011x00xxxx", ShaderDecode.Xmad_I);
- Set("010100010xxxxx", ShaderDecode.Xmad_RC);
- Set("0101101100xxxx", ShaderDecode.Xmad_RR);
-#endregion
- }
-
- private static void Set(string encoding, ShaderDecodeFunc func)
- {
- if (encoding.Length != EncodingBits)
- {
- throw new ArgumentException(nameof(encoding));
- }
-
- int bit = encoding.Length - 1;
- int value = 0;
- int xMask = 0;
- int xBits = 0;
-
- int[] xPos = new int[encoding.Length];
-
- for (int index = 0; index < encoding.Length; index++, bit--)
- {
- char chr = encoding[index];
-
- if (chr == '1')
- {
- value |= 1 << bit;
- }
- else if (chr == 'x')
- {
- xMask |= 1 << bit;
-
- xPos[xBits++] = bit;
- }
- }
-
- xMask = ~xMask;
-
- ShaderDecodeEntry entry = new ShaderDecodeEntry(func, xBits);
-
- for (int index = 0; index < (1 << xBits); index++)
- {
- value &= xMask;
-
- for (int x = 0; x < xBits; x++)
- {
- value |= ((index >> x) & 1) << xPos[x];
- }
-
- if (_opCodes[value] == null || _opCodes[value].XBits > xBits)
- {
- _opCodes[value] = entry;
- }
- }
- }
-
- public static ShaderDecodeFunc GetDecoder(long opCode)
- {
- return _opCodes[(ulong)opCode >> (64 - EncodingBits)]?.Func;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs
deleted file mode 100644
index 22a2ab85..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- enum ShaderOper
- {
- Cr,
- Imm,
- Immf,
- Rc,
- Rr
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderRegisterSize.cs b/Ryujinx.Graphics/Gal/Shader/ShaderRegisterSize.cs
deleted file mode 100644
index eb37359b..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderRegisterSize.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- enum ShaderRegisterSize
- {
- Half,
- Single,
- Double
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs b/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs
deleted file mode 100644
index 9158662c..00000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
- enum ShaderTexqInfo
- {
- Dimension = 1,
- TextureType = 2,
- SamplePos = 5,
- Filter = 16,
- Lod = 18,
- Wrap = 20,
- BorderColor = 22
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs
deleted file mode 100644
index f1f4650c..00000000
--- a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using Ryujinx.Graphics.Texture;
-
-namespace Ryujinx.Graphics.Gal
-{
- public class ShaderDeclInfo
- {
- public string Name { get; private set; }
-
- public int Index { get; private set; }
- public bool IsCb { get; private set; }
- public int Cbuf { get; private set; }
- public int Size { get; private set; }
-
- public GalTextureTarget TextureTarget { get; private set; }
-
- public TextureInstructionSuffix TextureSuffix { get; private set; }
-
- public ShaderDeclInfo(
- string name,
- int index,
- bool isCb = false,
- int cbuf = 0,
- int size = 1,
- GalTextureTarget textureTarget = GalTextureTarget.TwoD,
- TextureInstructionSuffix textureSuffix = TextureInstructionSuffix.None)
- {
- Name = name;
- Index = index;
- IsCb = isCb;
- Cbuf = cbuf;
- Size = size;
-
- TextureTarget = textureTarget;
- TextureSuffix = textureSuffix;
- }
-
- internal void Enlarge(int newSize)
- {
- if (Size < newSize)
- {
- Size = newSize;
- }
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs
index 605cbda8..bbed642b 100644
--- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs
+++ b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Memory;
+using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Texture;
using System;
using System.Collections.Generic;
@@ -464,7 +465,7 @@ namespace Ryujinx.Graphics.Graphics3d
left = _viewportX1 - (left - _viewportX0);
right = _viewportX1 - (right - _viewportX0);
}
-
+
// Ensure X is in the right order
if (left > right)
{
@@ -626,20 +627,22 @@ namespace Ryujinx.Graphics.Graphics3d
for (int index = 0; index < keys.Length; index++)
{
- foreach (ShaderDeclInfo declInfo in _gpu.Renderer.Shader.GetTextureUsage(keys[index]))
+ foreach (TextureDescriptor desc in _gpu.Renderer.Shader.GetTextureUsage(keys[index]))
{
- long position;
+ int textureHandle;
- if (declInfo.IsCb)
+ if (desc.IsBindless)
{
- position = _constBuffers[index][declInfo.Cbuf].Position;
+ long position = _constBuffers[index][desc.CbufSlot].Position;
+
+ textureHandle = vmm.ReadInt32(position + desc.CbufOffset * 4);
}
else
{
- position = _constBuffers[index][textureCbIndex].Position;
- }
+ long position = _constBuffers[index][textureCbIndex].Position;
- int textureHandle = vmm.ReadInt32(position + declInfo.Index * 4);
+ textureHandle = vmm.ReadInt32(position + desc.HandleIndex * 4);
+ }
unboundTextures.Add(UploadTexture(vmm, textureHandle));
}
@@ -712,9 +715,9 @@ namespace Ryujinx.Graphics.Graphics3d
{
for (int stage = 0; stage < keys.Length; stage++)
{
- foreach (ShaderDeclInfo declInfo in _gpu.Renderer.Shader.GetConstBufferUsage(keys[stage]))
+ foreach (CBufferDescriptor desc in _gpu.Renderer.Shader.GetConstBufferUsage(keys[stage]))
{
- ConstBuffer cb = _constBuffers[stage][declInfo.Cbuf];
+ ConstBuffer cb = _constBuffers[stage][desc.Slot];
if (!cb.Enabled)
{
@@ -735,7 +738,7 @@ namespace Ryujinx.Graphics.Graphics3d
}
}
- state.ConstBufferKeys[stage][declInfo.Cbuf] = key;
+ state.ConstBufferKeys[stage][desc.Slot] = key;
}
}
}
diff --git a/Ryujinx.Graphics/Shader/CBufferDescriptor.cs b/Ryujinx.Graphics/Shader/CBufferDescriptor.cs
new file mode 100644
index 00000000..f99665e1
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CBufferDescriptor.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Shader
+{
+ public struct CBufferDescriptor
+ {
+ public string Name { get; }
+
+ public int Slot { get; }
+
+ public CBufferDescriptor(string name, int slot)
+ {
+ Name = name;
+ Slot = slot;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs
new file mode 100644
index 00000000..ce5d7b94
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ class CodeGenContext
+ {
+ private const string Tab = " ";
+
+ public ShaderConfig Config { get; }
+
+ public List<CBufferDescriptor> CBufferDescriptors { get; }
+ public List<TextureDescriptor> TextureDescriptors { get; }
+
+ public OperandManager OperandManager { get; }
+
+ private StringBuilder _sb;
+
+ private int _level;
+
+ private string _identation;
+
+ public CodeGenContext(ShaderConfig config)
+ {
+ Config = config;
+
+ CBufferDescriptors = new List<CBufferDescriptor>();
+ TextureDescriptors = new List<TextureDescriptor>();
+
+ OperandManager = new OperandManager();
+
+ _sb = new StringBuilder();
+ }
+
+ public void AppendLine()
+ {
+ _sb.AppendLine();
+ }
+
+ public void AppendLine(string str)
+ {
+ _sb.AppendLine(_identation + str);
+ }
+
+ public string GetCode()
+ {
+ return _sb.ToString();
+ }
+
+ public void EnterScope()
+ {
+ AppendLine("{");
+
+ _level++;
+
+ UpdateIdentation();
+ }
+
+ public void LeaveScope(string suffix = "")
+ {
+ if (_level == 0)
+ {
+ return;
+ }
+
+ _level--;
+
+ UpdateIdentation();
+
+ AppendLine("}" + suffix);
+ }
+
+ private void UpdateIdentation()
+ {
+ _identation = GetIdentation(_level);
+ }
+
+ private static string GetIdentation(int level)
+ {
+ string identation = string.Empty;
+
+ for (int index = 0; index < level; index++)
+ {
+ identation += Tab;
+ }
+
+ return identation;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs
new file mode 100644
index 00000000..5412d872
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs
@@ -0,0 +1,206 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ static class Declarations
+ {
+ public static void Declare(CodeGenContext context, StructuredProgramInfo info)
+ {
+ context.AppendLine("#version 420 core");
+
+ context.AppendLine();
+
+ context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;");
+
+ context.AppendLine();
+
+ if (context.Config.Type == GalShaderType.Geometry)
+ {
+ context.AppendLine("layout (points) in;");
+ context.AppendLine("layout (triangle_strip, max_vertices = 4) out;");
+
+ context.AppendLine();
+ }
+
+ context.AppendLine("layout (std140) uniform Extra");
+
+ context.EnterScope();
+
+ context.AppendLine("vec2 flip;");
+ context.AppendLine("int instance;");
+
+ context.LeaveScope(";");
+
+ context.AppendLine();
+
+ if (info.CBuffers.Count != 0)
+ {
+ DeclareUniforms(context, info);
+
+ context.AppendLine();
+ }
+
+ if (info.Samplers.Count != 0)
+ {
+ DeclareSamplers(context, info);
+
+ context.AppendLine();
+ }
+
+ if (info.IAttributes.Count != 0)
+ {
+ DeclareInputAttributes(context, info);
+
+ context.AppendLine();
+ }
+
+ if (info.OAttributes.Count != 0)
+ {
+ DeclareOutputAttributes(context, info);
+
+ context.AppendLine();
+ }
+ }
+
+ public static void DeclareLocals(CodeGenContext context, StructuredProgramInfo info)
+ {
+ foreach (AstOperand decl in info.Locals)
+ {
+ string name = context.OperandManager.DeclareLocal(decl);
+
+ context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";");
+ }
+ }
+
+ private static string GetVarTypeName(VariableType type)
+ {
+ switch (type)
+ {
+ case VariableType.Bool: return "bool";
+ case VariableType.F32: return "float";
+ case VariableType.S32: return "int";
+ case VariableType.U32: return "uint";
+ }
+
+ throw new ArgumentException($"Invalid variable type \"{type}\".");
+ }
+
+ private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo info)
+ {
+ foreach (int cbufSlot in info.CBuffers.OrderBy(x => x))
+ {
+ string ubName = OperandManager.GetShaderStagePrefix(context.Config.Type);
+
+ ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot;
+
+ context.CBufferDescriptors.Add(new CBufferDescriptor(ubName, cbufSlot));
+
+ context.AppendLine("layout (std140) uniform " + ubName);
+
+ context.EnterScope();
+
+ string ubSize = "[" + NumberFormatter.FormatInt(context.Config.MaxCBufferSize / 16) + "]";
+
+ context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Type, cbufSlot) + ubSize + ";");
+
+ context.LeaveScope(";");
+ }
+ }
+
+ private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo info)
+ {
+ Dictionary<string, AstTextureOperation> samplers = new Dictionary<string, AstTextureOperation>();
+
+ foreach (AstTextureOperation texOp in info.Samplers.OrderBy(x => x.Handle))
+ {
+ string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp);
+
+ if (!samplers.TryAdd(samplerName, texOp))
+ {
+ continue;
+ }
+
+ string samplerTypeName = GetSamplerTypeName(texOp.Type);
+
+ context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";");
+ }
+
+ foreach (KeyValuePair<string, AstTextureOperation> kv in samplers)
+ {
+ string samplerName = kv.Key;
+
+ AstTextureOperation texOp = kv.Value;
+
+ TextureDescriptor desc;
+
+ if ((texOp.Flags & TextureFlags.Bindless) != 0)
+ {
+ AstOperand operand = texOp.GetSource(0) as AstOperand;
+
+ desc = new TextureDescriptor(samplerName, operand.CbufSlot, operand.CbufOffset);
+ }
+ else
+ {
+ desc = new TextureDescriptor(samplerName, texOp.Handle);
+ }
+
+ context.TextureDescriptors.Add(desc);
+ }
+ }
+
+ private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info)
+ {
+ string suffix = context.Config.Type == GalShaderType.Geometry ? "[]" : string.Empty;
+
+ foreach (int attr in info.IAttributes.OrderBy(x => x))
+ {
+ context.AppendLine($"layout (location = {attr}) in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};");
+ }
+ }
+
+ private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info)
+ {
+ foreach (int attr in info.OAttributes.OrderBy(x => x))
+ {
+ context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};");
+ }
+ }
+
+ private static string GetSamplerTypeName(TextureType type)
+ {
+ string typeName;
+
+ switch (type & TextureType.Mask)
+ {
+ case TextureType.Texture1D: typeName = "sampler1D"; break;
+ case TextureType.Texture2D: typeName = "sampler2D"; break;
+ case TextureType.Texture3D: typeName = "sampler3D"; break;
+ case TextureType.TextureCube: typeName = "samplerCube"; break;
+
+ default: throw new ArgumentException($"Invalid sampler type \"{type}\".");
+ }
+
+ if ((type & TextureType.Multisample) != 0)
+ {
+ typeName += "MS";
+ }
+
+ if ((type & TextureType.Array) != 0)
+ {
+ typeName += "Array";
+ }
+
+ if ((type & TextureType.Shadow) != 0)
+ {
+ typeName += "Shadow";
+ }
+
+ return typeName;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs
new file mode 100644
index 00000000..1d3939fb
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ static class DefaultNames
+ {
+ public const string LocalNamePrefix = "temp";
+
+ public const string SamplerNamePrefix = "tex";
+
+ public const string IAttributePrefix = "in_attr";
+ public const string OAttributePrefix = "out_attr";
+
+ public const string UniformNamePrefix = "c";
+ public const string UniformNameSuffix = "data";
+
+ public const string UndefinedName = "undef";
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs
new file mode 100644
index 00000000..4edbda8b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs
@@ -0,0 +1,133 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using System;
+
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ static class GlslGenerator
+ {
+ public static GlslProgram Generate(StructuredProgramInfo info, ShaderConfig config)
+ {
+ CodeGenContext context = new CodeGenContext(config);
+
+ Declarations.Declare(context, info);
+
+ PrintMainBlock(context, info);
+
+ return new GlslProgram(
+ context.CBufferDescriptors.ToArray(),
+ context.TextureDescriptors.ToArray(),
+ context.GetCode());
+ }
+
+ private static void PrintMainBlock(CodeGenContext context, StructuredProgramInfo info)
+ {
+ context.AppendLine("void main()");
+
+ context.EnterScope();
+
+ Declarations.DeclareLocals(context, info);
+
+ PrintBlock(context, info.MainBlock);
+
+ context.LeaveScope();
+ }
+
+ private static void PrintBlock(CodeGenContext context, AstBlock block)
+ {
+ AstBlockVisitor visitor = new AstBlockVisitor(block);
+
+ visitor.BlockEntered += (sender, e) =>
+ {
+ switch (e.Block.Type)
+ {
+ case AstBlockType.DoWhile:
+ context.AppendLine("do");
+ break;
+
+ case AstBlockType.Else:
+ context.AppendLine("else");
+ break;
+
+ case AstBlockType.ElseIf:
+ context.AppendLine($"else if ({GetCondExpr(context, e.Block.Condition)})");
+ break;
+
+ case AstBlockType.If:
+ context.AppendLine($"if ({GetCondExpr(context, e.Block.Condition)})");
+ break;
+
+ default: throw new InvalidOperationException($"Found unexpected block type \"{e.Block.Type}\".");
+ }
+
+ context.EnterScope();
+ };
+
+ visitor.BlockLeft += (sender, e) =>
+ {
+ context.LeaveScope();
+
+ if (e.Block.Type == AstBlockType.DoWhile)
+ {
+ context.AppendLine($"while ({GetCondExpr(context, e.Block.Condition)});");
+ }
+ };
+
+ foreach (IAstNode node in visitor.Visit())
+ {
+ if (node is AstOperation operation)
+ {
+ if (operation.Inst == Instruction.Return)
+ {
+ PrepareForReturn(context);
+ }
+
+ context.AppendLine(InstGen.GetExpression(context, operation) + ";");
+ }
+ else if (node is AstAssignment assignment)
+ {
+ VariableType srcType = OperandManager.GetNodeDestType(assignment.Source);
+ VariableType dstType = OperandManager.GetNodeDestType(assignment.Destination);
+
+ string dest;
+
+ if (assignment.Destination is AstOperand operand && operand.Type == OperandType.Attribute)
+ {
+ dest = OperandManager.GetOutAttributeName(operand, context.Config.Type);
+ }
+ else
+ {
+ dest = InstGen.GetExpression(context, assignment.Destination);
+ }
+
+ string src = ReinterpretCast(context, assignment.Source, srcType, dstType);
+
+ context.AppendLine(dest + " = " + src + ";");
+ }
+ else
+ {
+ throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\".");
+ }
+ }
+ }
+
+ private static string GetCondExpr(CodeGenContext context, IAstNode cond)
+ {
+ VariableType srcType = OperandManager.GetNodeDestType(cond);
+
+ return ReinterpretCast(context, cond, srcType, VariableType.Bool);
+ }
+
+ private static void PrepareForReturn(CodeGenContext context)
+ {
+ if (context.Config.Type == GalShaderType.Vertex)
+ {
+ context.AppendLine("gl_Position.xy *= flip;");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs
new file mode 100644
index 00000000..e616aa1f
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ class GlslProgram
+ {
+ public CBufferDescriptor[] CBufferDescriptors { get; }
+ public TextureDescriptor[] TextureDescriptors { get; }
+
+ public string Code { get; }
+
+ public GlslProgram(
+ CBufferDescriptor[] cBufferDescs,
+ TextureDescriptor[] textureDescs,
+ string code)
+ {
+ CBufferDescriptors = cBufferDescs;
+ TextureDescriptors = textureDescs;
+ Code = code;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs
new file mode 100644
index 00000000..b0b2ec1a
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs
@@ -0,0 +1,110 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using System;
+
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
+using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
+{
+ static class InstGen
+ {
+ public static string GetExpression(CodeGenContext context, IAstNode node)
+ {
+ if (node is AstOperation operation)
+ {
+ return GetExpression(context, operation);
+ }
+ else if (node is AstOperand operand)
+ {
+ return context.OperandManager.GetExpression(operand, context.Config.Type);
+ }
+
+ throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
+ }
+
+ private static string GetExpression(CodeGenContext context, AstOperation operation)
+ {
+ Instruction inst = operation.Inst;
+
+ InstInfo info = GetInstructionInfo(inst);
+
+ if ((info.Type & InstType.Call) != 0)
+ {
+ int arity = (int)(info.Type & InstType.ArityMask);
+
+ string args = string.Empty;
+
+ for (int argIndex = 0; argIndex < arity; argIndex++)
+ {
+ if (argIndex != 0)
+ {
+ args += ", ";
+ }
+
+ VariableType dstType = GetSrcVarType(inst, argIndex);
+
+ args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
+ }
+
+ return info.OpName + "(" + args + ")";
+ }
+ else if ((info.Type & InstType.Op) != 0)
+ {
+ string op = info.OpName;
+
+ int arity = (int)(info.Type & InstType.ArityMask);
+
+ string[] expr = new string[arity];
+
+ for (int index = 0; index < arity; index++)
+ {
+ IAstNode src = operation.GetSource(index);
+
+ string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index));
+
+ bool isLhs = arity == 2 && index == 0;
+
+ expr[index] = Enclose(srcExpr, src, inst, info, isLhs);
+ }
+
+ switch (arity)
+ {
+ case 0:
+ return op;
+
+ case 1:
+ return op + expr[0];
+
+ case 2:
+ return $"{expr[0]} {op} {expr[1]}";
+
+ case 3:
+ return $"{expr[0]} {op[0]} {expr[1]} {op[1]} {expr[2]}";
+ }
+ }
+ else if ((info.Type & InstType.Special) != 0)
+ {
+ switch (inst)
+ {
+ case Instruction.LoadConstant:
+ return InstGenMemory.LoadConstant(context, operation);
+
+ case Instruction.PackHalf2x16:
+ return InstGenPacking.PackHalf2x16(context, operation);
+
+ case Instruction.TextureSample:
+ return InstGenMemory.TextureSample(context, operation);
+
+ case Instruction.TextureSize:
+ return InstGenMemory.TextureSize(context, operation);
+
+ case Instruction.UnpackHalf2x16:
+ return InstGenPacking.UnpackHalf2x16(context, operation);
+ }
+ }
+
+ throw new InvalidOperationException($"Unexpected instruction type \"{info.Type}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
new file mode 100644
index 00000000..0b860072
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
@@ -0,0 +1,170 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
+{
+ static class InstGenHelper
+ {
+ private static InstInfo[] _infoTbl;
+
+ static InstGenHelper()
+ {
+ _infoTbl = new InstInfo[(int)Instruction.Count];
+
+ Add(Instruction.Absolute, InstType.CallUnary, "abs");
+ Add(Instruction.Add, InstType.OpBinaryCom, "+", 2);
+ Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "bitfieldExtract");
+ Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "bitfieldExtract");
+ Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "bitfieldInsert");
+ Add(Instruction.BitfieldReverse, InstType.CallUnary, "bitfieldReverse");
+ Add(Instruction.BitwiseAnd, InstType.OpBinaryCom, "&", 6);
+ Add(Instruction.BitwiseExclusiveOr, InstType.OpBinaryCom, "^", 7);
+ Add(Instruction.BitwiseNot, InstType.OpUnary, "~", 0);
+ Add(Instruction.BitwiseOr, InstType.OpBinaryCom, "|", 8);
+ Add(Instruction.Ceiling, InstType.CallUnary, "ceil");
+ Add(Instruction.Clamp, InstType.CallTernary, "clamp");
+ Add(Instruction.ClampU32, InstType.CallTernary, "clamp");
+ Add(Instruction.CompareEqual, InstType.OpBinaryCom, "==", 5);
+ Add(Instruction.CompareGreater, InstType.OpBinary, ">", 4);
+ Add(Instruction.CompareGreaterOrEqual, InstType.OpBinary, ">=", 4);
+ Add(Instruction.CompareGreaterOrEqualU32, InstType.OpBinary, ">=", 4);
+ Add(Instruction.CompareGreaterU32, InstType.OpBinary, ">", 4);
+ Add(Instruction.CompareLess, InstType.OpBinary, "<", 4);
+ Add(Instruction.CompareLessOrEqual, InstType.OpBinary, "<=", 4);
+ Add(Instruction.CompareLessOrEqualU32, InstType.OpBinary, "<=", 4);
+ Add(Instruction.CompareLessU32, InstType.OpBinary, "<", 4);
+ Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5);
+ Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12);
+ Add(Instruction.ConvertFPToS32, InstType.CallUnary, "int");
+ Add(Instruction.ConvertS32ToFP, InstType.CallUnary, "float");
+ Add(Instruction.ConvertU32ToFP, InstType.CallUnary, "float");
+ Add(Instruction.Cosine, InstType.CallUnary, "cos");
+ Add(Instruction.Discard, InstType.OpNullary, "discard");
+ Add(Instruction.Divide, InstType.OpBinary, "/", 1);
+ Add(Instruction.EmitVertex, InstType.CallNullary, "EmitVertex");
+ Add(Instruction.EndPrimitive, InstType.CallNullary, "EndPrimitive");
+ Add(Instruction.ExponentB2, InstType.CallUnary, "exp2");
+ Add(Instruction.Floor, InstType.CallUnary, "floor");
+ Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma");
+ Add(Instruction.IsNan, InstType.CallUnary, "isnan");
+ Add(Instruction.LoadConstant, InstType.Special);
+ Add(Instruction.LogarithmB2, InstType.CallUnary, "log2");
+ Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9);
+ Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^^", 10);
+ Add(Instruction.LogicalNot, InstType.OpUnary, "!", 0);
+ Add(Instruction.LogicalOr, InstType.OpBinaryCom, "||", 11);
+ Add(Instruction.LoopBreak, InstType.OpNullary, "break");
+ Add(Instruction.LoopContinue, InstType.OpNullary, "continue");
+ Add(Instruction.PackHalf2x16, InstType.Special);
+ Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3);
+ Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3);
+ Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3);
+ Add(Instruction.Maximum, InstType.CallBinary, "max");
+ Add(Instruction.MaximumU32, InstType.CallBinary, "max");
+ Add(Instruction.Minimum, InstType.CallBinary, "min");
+ Add(Instruction.MinimumU32, InstType.CallBinary, "min");
+ Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
+ Add(Instruction.Negate, InstType.OpUnary, "-", 0);
+ Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "inversesqrt");
+ Add(Instruction.Return, InstType.OpNullary, "return");
+ Add(Instruction.Sine, InstType.CallUnary, "sin");
+ Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt");
+ Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
+ Add(Instruction.TextureSample, InstType.Special);
+ Add(Instruction.TextureSize, InstType.Special);
+ Add(Instruction.Truncate, InstType.CallUnary, "trunc");
+ Add(Instruction.UnpackHalf2x16, InstType.Special);
+ }
+
+ private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0)
+ {
+ _infoTbl[(int)inst] = new InstInfo(flags, opName, precedence);
+ }
+
+ public static InstInfo GetInstructionInfo(Instruction inst)
+ {
+ return _infoTbl[(int)(inst & Instruction.Mask)];
+ }
+
+ public static string GetSoureExpr(CodeGenContext context, IAstNode node, VariableType dstType)
+ {
+ return ReinterpretCast(context, node, OperandManager.GetNodeDestType(node), dstType);
+ }
+
+ public static string Enclose(string expr, IAstNode node, Instruction pInst, bool isLhs)
+ {
+ InstInfo pInfo = GetInstructionInfo(pInst);
+
+ return Enclose(expr, node, pInst, pInfo, isLhs);
+ }
+
+ public static string Enclose(string expr, IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs = false)
+ {
+ if (NeedsParenthesis(node, pInst, pInfo, isLhs))
+ {
+ expr = "(" + expr + ")";
+ }
+
+ return expr;
+ }
+
+ public static bool NeedsParenthesis(IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs)
+ {
+ //If the node isn't a operation, then it can only be a operand,
+ //and those never needs to be surrounded in parenthesis.
+ if (!(node is AstOperation operation))
+ {
+ //This is sort of a special case, if this is a negative constant,
+ //and it is consumed by a unary operation, we need to put on the parenthesis,
+ //as in GLSL a sequence like --2 or ~-1 is not valid.
+ if (IsNegativeConst(node) && pInfo.Type == InstType.OpUnary)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ if ((pInfo.Type & (InstType.Call | InstType.Special)) != 0)
+ {
+ return false;
+ }
+
+ InstInfo info = _infoTbl[(int)(operation.Inst & Instruction.Mask)];
+
+ if ((info.Type & (InstType.Call | InstType.Special)) != 0)
+ {
+ return false;
+ }
+
+ if (info.Precedence < pInfo.Precedence)
+ {
+ return false;
+ }
+
+ if (info.Precedence == pInfo.Precedence && isLhs)
+ {
+ return false;
+ }
+
+ if (pInst == operation.Inst && info.Type == InstType.OpBinaryCom)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool IsNegativeConst(IAstNode node)
+ {
+ if (!(node is AstOperand operand))
+ {
+ return false;
+ }
+
+ return operand.Type == OperandType.Constant && operand.Value < 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
new file mode 100644
index 00000000..79f80057
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -0,0 +1,244 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
+using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
+{
+ static class InstGenMemory
+ {
+ public static string LoadConstant(CodeGenContext context, AstOperation operation)
+ {
+ IAstNode src1 = operation.GetSource(1);
+
+ string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1));
+
+ offsetExpr = Enclose(offsetExpr, src1, Instruction.ShiftRightS32, isLhs: true);
+
+ return OperandManager.GetConstantBufferName(operation.GetSource(0), offsetExpr, context.Config.Type);
+ }
+
+ public static string TextureSample(CodeGenContext context, AstOperation operation)
+ {
+ AstTextureOperation texOp = (AstTextureOperation)operation;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+ bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
+ bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
+ bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0;
+ bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
+ bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
+ bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
+ bool isArray = (texOp.Type & TextureType.Array) != 0;
+ bool isMultisample = (texOp.Type & TextureType.Multisample) != 0;
+ bool isShadow = (texOp.Type & TextureType.Shadow) != 0;
+
+ string texCall = intCoords ? "texelFetch" : "texture";
+
+ if (isGather)
+ {
+ texCall += "Gather";
+ }
+ else if (hasLodLevel && !intCoords)
+ {
+ texCall += "Lod";
+ }
+
+ if (hasOffset)
+ {
+ texCall += "Offset";
+ }
+ else if (hasOffsets)
+ {
+ texCall += "Offsets";
+ }
+
+ string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp);
+
+ texCall += "(" + samplerName;
+
+ int coordsCount = texOp.Type.GetCoordsCount();
+
+ int pCount = coordsCount;
+
+ int arrayIndexElem = -1;
+
+ if (isArray)
+ {
+ arrayIndexElem = pCount++;
+ }
+
+ //The sampler 1D shadow overload expects a
+ //dummy value on the middle of the vector, who knows why...
+ bool hasDummy1DShadowElem = texOp.Type == (TextureType.Texture1D | TextureType.Shadow);
+
+ if (hasDummy1DShadowElem)
+ {
+ pCount++;
+ }
+
+ if (isShadow && !isGather)
+ {
+ pCount++;
+ }
+
+ //On textureGather*, the comparison value is
+ //always specified as an extra argument.
+ bool hasExtraCompareArg = isShadow && isGather;
+
+ if (pCount == 5)
+ {
+ pCount = 4;
+
+ hasExtraCompareArg = true;
+ }
+
+ int srcIndex = isBindless ? 1 : 0;
+
+ string Src(VariableType type)
+ {
+ return GetSoureExpr(context, texOp.GetSource(srcIndex++), type);
+ }
+
+ void Append(string str)
+ {
+ texCall += ", " + str;
+ }
+
+ VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32;
+
+ string AssemblePVector(int count)
+ {
+ if (count > 1)
+ {
+ string[] elems = new string[count];
+
+ for (int index = 0; index < count; index++)
+ {
+ if (arrayIndexElem == index)
+ {
+ elems[index] = Src(VariableType.S32);
+
+ if (!intCoords)
+ {
+ elems[index] = "float(" + elems[index] + ")";
+ }
+ }
+ else if (index == 1 && hasDummy1DShadowElem)
+ {
+ elems[index] = NumberFormatter.FormatFloat(0);
+ }
+ else
+ {
+ elems[index] = Src(coordType);
+ }
+ }
+
+ string prefix = intCoords ? "i" : string.Empty;
+
+ return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")";
+ }
+ else
+ {
+ return Src(coordType);
+ }
+ }
+
+ Append(AssemblePVector(pCount));
+
+ if (hasExtraCompareArg)
+ {
+ Append(Src(VariableType.F32));
+ }
+
+ if (isMultisample)
+ {
+ Append(Src(VariableType.S32));
+ }
+ else if (hasLodLevel)
+ {
+ Append(Src(coordType));
+ }
+
+ string AssembleOffsetVector(int count)
+ {
+ if (count > 1)
+ {
+ string[] elems = new string[count];
+
+ for (int index = 0; index < count; index++)
+ {
+ elems[index] = Src(VariableType.S32);
+ }
+
+ return "ivec" + count + "(" + string.Join(", ", elems) + ")";
+ }
+ else
+ {
+ return Src(VariableType.S32);
+ }
+ }
+
+ if (hasOffset)
+ {
+ Append(AssembleOffsetVector(coordsCount));
+ }
+ else if (hasOffsets)
+ {
+ texCall += $", ivec{coordsCount}[4](";
+
+ texCall += AssembleOffsetVector(coordsCount) + ", ";
+ texCall += AssembleOffsetVector(coordsCount) + ", ";
+ texCall += AssembleOffsetVector(coordsCount) + ", ";
+ texCall += AssembleOffsetVector(coordsCount) + ")";
+ }
+
+ if (hasLodBias)
+ {
+ Append(Src(VariableType.F32));
+ }
+
+ //textureGather* optional extra component index,
+ //not needed for shadow samplers.
+ if (isGather && !isShadow)
+ {
+ Append(Src(VariableType.S32));
+ }
+
+ texCall += ")" + (isGather || !isShadow ? GetMask(texOp.ComponentMask) : "");
+
+ return texCall;
+ }
+
+ public static string TextureSize(CodeGenContext context, AstOperation operation)
+ {
+ AstTextureOperation texOp = (AstTextureOperation)operation;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+
+ string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp);
+
+ IAstNode src0 = operation.GetSource(isBindless ? 1 : 0);
+
+ string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0));
+
+ return $"textureSize({samplerName}, {src0Expr}){GetMask(texOp.ComponentMask)}";
+ }
+
+ private static string GetMask(int compMask)
+ {
+ string mask = ".";
+
+ for (int index = 0; index < 4; index++)
+ {
+ if ((compMask & (1 << index)) != 0)
+ {
+ mask += "rgba".Substring(index, 1);
+ }
+ }
+
+ return mask;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs
new file mode 100644
index 00000000..4a40032c
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs
@@ -0,0 +1,45 @@
+using Ryujinx.Graphics.Shader.StructuredIr;
+
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
+using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
+{
+ static class InstGenPacking
+ {
+ public static string PackHalf2x16(CodeGenContext context, AstOperation operation)
+ {
+ IAstNode src0 = operation.GetSource(0);
+ IAstNode src1 = operation.GetSource(1);
+
+ string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0));
+ string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1));
+
+ return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))";
+ }
+
+ public static string UnpackHalf2x16(CodeGenContext context, AstOperation operation)
+ {
+ IAstNode src = operation.GetSource(0);
+
+ string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0));
+
+ return $"unpackHalf2x16({srcExpr}){GetMask(operation.ComponentMask)}";
+ }
+
+ private static string GetMask(int compMask)
+ {
+ string mask = ".";
+
+ for (int index = 0; index < 2; index++)
+ {
+ if ((compMask & (1 << index)) != 0)
+ {
+ mask += "xy".Substring(index, 1);
+ }
+ }
+
+ return mask;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs
new file mode 100644
index 00000000..fc9aef7e
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
+{
+ struct InstInfo
+ {
+ public InstType Type { get; }
+
+ public string OpName { get; }
+
+ public int Precedence { get; }
+
+ public InstInfo(InstType type, string opName, int precedence)
+ {
+ Type = type;
+ OpName = opName;
+ Precedence = precedence;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs
new file mode 100644
index 00000000..7d38a9d2
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
+{
+ [Flags]
+ enum InstType
+ {
+ OpNullary = Op | 0,
+ OpUnary = Op | 1,
+ OpBinary = Op | 2,
+ OpTernary = Op | 3,
+ OpBinaryCom = OpBinary | Comutative,
+
+ CallNullary = Call | 0,
+ CallUnary = Call | 1,
+ CallBinary = Call | 2,
+ CallTernary = Call | 3,
+ CallQuaternary = Call | 4,
+
+ Comutative = 1 << 8,
+ Op = 1 << 9,
+ Call = 1 << 10,
+ Special = 1 << 11,
+
+ ArityMask = 0xff
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs
new file mode 100644
index 00000000..2ec44277
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs
@@ -0,0 +1,104 @@
+using Ryujinx.Graphics.Shader.StructuredIr;
+using System;
+using System.Globalization;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ static class NumberFormatter
+ {
+ private const int MaxDecimal = 256;
+
+ public static bool TryFormat(int value, VariableType dstType, out string formatted)
+ {
+ if (dstType == VariableType.F32)
+ {
+ return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted);
+ }
+ else if (dstType == VariableType.S32)
+ {
+ formatted = FormatInt(value);
+ }
+ else if (dstType == VariableType.U32)
+ {
+ formatted = FormatUint((uint)value);
+ }
+ else if (dstType == VariableType.Bool)
+ {
+ formatted = value != 0 ? "true" : "false";
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid variable type \"{dstType}\".");
+ }
+
+ return true;
+ }
+
+ public static string FormatFloat(float value)
+ {
+ if (!TryFormatFloat(value, out string formatted))
+ {
+ throw new ArgumentException("Failed to convert float value to string.");
+ }
+
+ return formatted;
+ }
+
+ public static bool TryFormatFloat(float value, out string formatted)
+ {
+ if (float.IsNaN(value) || float.IsInfinity(value))
+ {
+ formatted = null;
+
+ return false;
+ }
+
+ formatted = value.ToString("G9", CultureInfo.InvariantCulture);
+
+ if (!(formatted.Contains('.') ||
+ formatted.Contains('e') ||
+ formatted.Contains('E')))
+ {
+ formatted += ".0";
+ }
+
+ return true;
+ }
+
+ public static string FormatInt(int value, VariableType dstType)
+ {
+ if (dstType == VariableType.S32)
+ {
+ return FormatInt(value);
+ }
+ else if (dstType == VariableType.U32)
+ {
+ return FormatUint((uint)value);
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid variable type \"{dstType}\".");
+ }
+ }
+
+ public static string FormatInt(int value)
+ {
+ if (value <= MaxDecimal && value >= -MaxDecimal)
+ {
+ return value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ return "0x" + value.ToString("X", CultureInfo.InvariantCulture);
+ }
+
+ public static string FormatUint(uint value)
+ {
+ if (value <= MaxDecimal && value >= 0)
+ {
+ return value.ToString(CultureInfo.InvariantCulture) + "u";
+ }
+
+ return "0x" + value.ToString("X", CultureInfo.InvariantCulture) + "u";
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs
new file mode 100644
index 00000000..debba428
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs
@@ -0,0 +1,239 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ class OperandManager
+ {
+ private static string[] _stagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" };
+
+ private struct BuiltInAttribute
+ {
+ public string Name { get; }
+
+ public VariableType Type { get; }
+
+ public BuiltInAttribute(string name, VariableType type)
+ {
+ Name = name;
+ Type = type;
+ }
+ }
+
+ private static Dictionary<int, BuiltInAttribute> _builtInAttributes =
+ new Dictionary<int, BuiltInAttribute>()
+ {
+ { AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", VariableType.S32) },
+ { AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) },
+ { AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) },
+ { AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", VariableType.F32) },
+ { AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", VariableType.F32) },
+ { AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", VariableType.F32) },
+ { AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", VariableType.F32) },
+ { AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", VariableType.F32) },
+ { AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", VariableType.F32) },
+ { AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", VariableType.F32) },
+ { AttributeConsts.InstanceId, new BuiltInAttribute("instance", VariableType.S32) },
+ { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) },
+ { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) },
+ { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) }
+ };
+
+ private Dictionary<AstOperand, string> _locals;
+
+ public OperandManager()
+ {
+ _locals = new Dictionary<AstOperand, string>();
+ }
+
+ public string DeclareLocal(AstOperand operand)
+ {
+ string name = $"{DefaultNames.LocalNamePrefix}_{_locals.Count}";
+
+ _locals.Add(operand, name);
+
+ return name;
+ }
+
+ public string GetExpression(AstOperand operand, GalShaderType shaderType)
+ {
+ switch (operand.Type)
+ {
+ case OperandType.Attribute:
+ return GetAttributeName(operand, shaderType);
+
+ case OperandType.Constant:
+ return NumberFormatter.FormatInt(operand.Value);
+
+ case OperandType.ConstantBuffer:
+ return GetConstantBufferName(operand, shaderType);
+
+ case OperandType.LocalVariable:
+ return _locals[operand];
+
+ case OperandType.Undefined:
+ return DefaultNames.UndefinedName;
+ }
+
+ throw new ArgumentException($"Invalid operand type \"{operand.Type}\".");
+ }
+
+ public static string GetConstantBufferName(AstOperand cbuf, GalShaderType shaderType)
+ {
+ string ubName = GetUbName(shaderType, cbuf.CbufSlot);
+
+ ubName += "[" + (cbuf.CbufOffset >> 2) + "]";
+
+ return ubName + "." + GetSwizzleMask(cbuf.CbufOffset & 3);
+ }
+
+ public static string GetConstantBufferName(IAstNode slot, string offsetExpr, GalShaderType shaderType)
+ {
+ //Non-constant slots are not supported.
+ //It is expected that upstream stages are never going to generate non-constant
+ //slot access.
+ AstOperand operand = (AstOperand)slot;
+
+ string ubName = GetUbName(shaderType, operand.Value);
+
+ string index0 = "[" + offsetExpr + " >> 4]";
+ string index1 = "[" + offsetExpr + " >> 2 & 3]";
+
+ return ubName + index0 + index1;
+ }
+
+ public static string GetOutAttributeName(AstOperand attr, GalShaderType shaderType)
+ {
+ return GetAttributeName(attr, shaderType, isOutAttr: true);
+ }
+
+ private static string GetAttributeName(AstOperand attr, GalShaderType shaderType, bool isOutAttr = false)
+ {
+ int value = attr.Value;
+
+ string swzMask = GetSwizzleMask((value >> 2) & 3);
+
+ if (value >= AttributeConsts.UserAttributeBase &&
+ value < AttributeConsts.UserAttributeEnd)
+ {
+ value -= AttributeConsts.UserAttributeBase;
+
+ string prefix = isOutAttr
+ ? DefaultNames.OAttributePrefix
+ : DefaultNames.IAttributePrefix;
+
+ string name = $"{prefix}{(value >> 4)}";
+
+ if (shaderType == GalShaderType.Geometry && !isOutAttr)
+ {
+ name += "[0]";
+ }
+
+ name += "." + swzMask;
+
+ return name;
+ }
+ else
+ {
+ if (value >= AttributeConsts.FragmentOutputColorBase &&
+ value < AttributeConsts.FragmentOutputColorEnd)
+ {
+ value -= AttributeConsts.FragmentOutputColorBase;
+
+ return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}";
+ }
+ else if (_builtInAttributes.TryGetValue(value & ~3, out BuiltInAttribute builtInAttr))
+ {
+ //TODO: There must be a better way to handle this...
+ if (shaderType == GalShaderType.Fragment)
+ {
+ switch (value & ~3)
+ {
+ case AttributeConsts.PositionX: return "gl_FragCoord.x";
+ case AttributeConsts.PositionY: return "gl_FragCoord.y";
+ case AttributeConsts.PositionZ: return "gl_FragCoord.z";
+ case AttributeConsts.PositionW: return "1.0";
+ }
+ }
+
+ string name = builtInAttr.Name;
+
+ if (shaderType == GalShaderType.Geometry && !isOutAttr)
+ {
+ name = "gl_in[0]." + name;
+ }
+
+ return name;
+ }
+ }
+
+ return DefaultNames.UndefinedName;
+ }
+
+ public static string GetUbName(GalShaderType shaderType, int slot)
+ {
+ string ubName = OperandManager.GetShaderStagePrefix(shaderType);
+
+ ubName += "_" + DefaultNames.UniformNamePrefix + slot;
+
+ return ubName + "_" + DefaultNames.UniformNameSuffix;
+ }
+
+ public static string GetSamplerName(GalShaderType shaderType, AstTextureOperation texOp)
+ {
+ string suffix;
+
+ if ((texOp.Flags & TextureFlags.Bindless) != 0)
+ {
+ AstOperand operand = texOp.GetSource(0) as AstOperand;
+
+ suffix = "_cb" + operand.CbufSlot + "_" + operand.CbufOffset;
+ }
+ else
+ {
+ suffix = (texOp.Handle - 8).ToString();
+ }
+
+ return GetShaderStagePrefix(shaderType) + "_" + DefaultNames.SamplerNamePrefix + suffix;
+ }
+
+ public static string GetShaderStagePrefix(GalShaderType shaderType)
+ {
+ return _stagePrefixes[(int)shaderType];
+ }
+
+ private static string GetSwizzleMask(int value)
+ {
+ return "xyzw".Substring(value, 1);
+ }
+
+ public static VariableType GetNodeDestType(IAstNode node)
+ {
+ if (node is AstOperation operation)
+ {
+ return GetDestVarType(operation.Inst);
+ }
+ else if (node is AstOperand operand)
+ {
+ if (operand.Type == OperandType.Attribute)
+ {
+ if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
+ {
+ return builtInAttr.Type;
+ }
+ }
+
+ return OperandInfo.GetVarType(operand);
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs
new file mode 100644
index 00000000..7adc5ad3
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs
@@ -0,0 +1,85 @@
+using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using System;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ static class TypeConversion
+ {
+ public static string ReinterpretCast(
+ CodeGenContext context,
+ IAstNode node,
+ VariableType srcType,
+ VariableType dstType)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.Constant)
+ {
+ if (NumberFormatter.TryFormat(operand.Value, dstType, out string formatted))
+ {
+ return formatted;
+ }
+ }
+
+ string expr = InstGen.GetExpression(context, node);
+
+ return ReinterpretCast(expr, node, srcType, dstType);
+ }
+
+ private static string ReinterpretCast(string expr, IAstNode node, VariableType srcType, VariableType dstType)
+ {
+ if (srcType == dstType)
+ {
+ return expr;
+ }
+
+ if (srcType == VariableType.F32)
+ {
+ switch (dstType)
+ {
+ case VariableType.S32: return $"floatBitsToInt({expr})";
+ case VariableType.U32: return $"floatBitsToUint({expr})";
+ }
+ }
+ else if (dstType == VariableType.F32)
+ {
+ switch (srcType)
+ {
+ case VariableType.Bool: return $"intBitsToFloat({ReinterpretBoolToInt(expr, node, VariableType.S32)})";
+ case VariableType.S32: return $"intBitsToFloat({expr})";
+ case VariableType.U32: return $"uintBitsToFloat({expr})";
+ }
+ }
+ else if (srcType == VariableType.Bool)
+ {
+ return ReinterpretBoolToInt(expr, node, dstType);
+ }
+ else if (dstType == VariableType.Bool)
+ {
+ expr = InstGenHelper.Enclose(expr, node, Instruction.CompareNotEqual, isLhs: true);
+
+ return $"({expr} != 0)";
+ }
+ else if (dstType == VariableType.S32)
+ {
+ return $"int({expr})";
+ }
+ else if (dstType == VariableType.U32)
+ {
+ return $"uint({expr})";
+ }
+
+ throw new ArgumentException($"Invalid reinterpret cast from \"{srcType}\" to \"{dstType}\".");
+ }
+
+ private static string ReinterpretBoolToInt(string expr, IAstNode node, VariableType dstType)
+ {
+ string trueExpr = NumberFormatter.FormatInt(IrConsts.True, dstType);
+ string falseExpr = NumberFormatter.FormatInt(IrConsts.False, dstType);
+
+ expr = InstGenHelper.Enclose(expr, node, Instruction.ConditionalSelect, isLhs: false);
+
+ return $"({expr} ? {trueExpr} : {falseExpr})";
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs b/Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs
new file mode 100644
index 00000000..3bb9bc1f
--- /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 00000000..b5e610d7
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/Block.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class Block
+ {
+ public ulong Address { get; set; }
+ public ulong EndAddress { get; set; }
+
+ public Block Next { get; set; }
+ public Block Branch { get; set; }
+
+ public List<OpCode> OpCodes { get; }
+ public List<OpCodeSsy> SsyOpCodes { get; }
+
+ public Block(ulong address)
+ {
+ Address = address;
+
+ OpCodes = new List<OpCode>();
+ SsyOpCodes = new List<OpCodeSsy>();
+ }
+
+ public void Split(Block rightBlock)
+ {
+ int splitIndex = BinarySearch(OpCodes, rightBlock.Address);
+
+ if (OpCodes[splitIndex].Address < rightBlock.Address)
+ {
+ splitIndex++;
+ }
+
+ int splitCount = OpCodes.Count - splitIndex;
+
+ if (splitCount <= 0)
+ {
+ throw new ArgumentException("Can't split at right block address.");
+ }
+
+ rightBlock.EndAddress = EndAddress;
+
+ rightBlock.Next = Next;
+ rightBlock.Branch = Branch;
+
+ rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount));
+
+ rightBlock.UpdateSsyOpCodes();
+
+ EndAddress = rightBlock.Address;
+
+ Next = rightBlock;
+ Branch = null;
+
+ OpCodes.RemoveRange(splitIndex, splitCount);
+
+ UpdateSsyOpCodes();
+ }
+
+ private static int BinarySearch(List<OpCode> opCodes, ulong address)
+ {
+ int left = 0;
+ int middle = 0;
+ int right = opCodes.Count - 1;
+
+ while (left <= right)
+ {
+ int size = right - left;
+
+ middle = left + (size >> 1);
+
+ OpCode opCode = opCodes[middle];
+
+ if (address == opCode.Address)
+ {
+ break;
+ }
+
+ if (address < opCode.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return middle;
+ }
+
+ public OpCode GetLastOp()
+ {
+ if (OpCodes.Count != 0)
+ {
+ return OpCodes[OpCodes.Count - 1];
+ }
+
+ return null;
+ }
+
+ public void UpdateSsyOpCodes()
+ {
+ SsyOpCodes.Clear();
+
+ for (int index = 0; index < OpCodes.Count; index++)
+ {
+ if (!(OpCodes[index] is OpCodeSsy op))
+ {
+ continue;
+ }
+
+ SsyOpCodes.Add(op);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/Condition.cs b/Ryujinx.Graphics/Shader/Decoders/Condition.cs
new file mode 100644
index 00000000..10400f94
--- /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 00000000..4fc31e84
--- /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 00000000..86df3b20
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/Decoder.cs
@@ -0,0 +1,406 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.Instructions;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Emit;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ static class Decoder
+ {
+ private const long HeaderSize = 0x50;
+
+ private delegate object OpActivator(InstEmitter emitter, ulong address, long opCode);
+
+ private static ConcurrentDictionary<Type, OpActivator> _opActivators;
+
+ static Decoder()
+ {
+ _opActivators = new ConcurrentDictionary<Type, OpActivator>();
+ }
+
+ public static Block[] Decode(IGalMemory memory, ulong address)
+ {
+ List<Block> blocks = new List<Block>();
+
+ Queue<Block> workQueue = new Queue<Block>();
+
+ Dictionary<ulong, Block> visited = new Dictionary<ulong, Block>();
+
+ Block GetBlock(ulong blkAddress)
+ {
+ if (!visited.TryGetValue(blkAddress, out Block block))
+ {
+ block = new Block(blkAddress);
+
+ workQueue.Enqueue(block);
+
+ visited.Add(blkAddress, block);
+ }
+
+ return block;
+ }
+
+ ulong startAddress = address + HeaderSize;
+
+ GetBlock(startAddress);
+
+ while (workQueue.TryDequeue(out Block currBlock))
+ {
+ //Check if the current block is inside another block.
+ if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex))
+ {
+ Block nBlock = blocks[nBlkIndex];
+
+ if (nBlock.Address == currBlock.Address)
+ {
+ throw new InvalidOperationException("Found duplicate block address on the list.");
+ }
+
+ nBlock.Split(currBlock);
+
+ blocks.Insert(nBlkIndex + 1, currBlock);
+
+ continue;
+ }
+
+ //If we have a block after the current one, set the limit address.
+ ulong limitAddress = ulong.MaxValue;
+
+ if (nBlkIndex != blocks.Count)
+ {
+ Block nBlock = blocks[nBlkIndex];
+
+ int nextIndex = nBlkIndex + 1;
+
+ if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count)
+ {
+ limitAddress = blocks[nextIndex].Address;
+ }
+ else if (nBlock.Address > currBlock.Address)
+ {
+ limitAddress = blocks[nBlkIndex].Address;
+ }
+ }
+
+ FillBlock(memory, currBlock, limitAddress, startAddress);
+
+ if (currBlock.OpCodes.Count != 0)
+ {
+ foreach (OpCodeSsy ssyOp in currBlock.SsyOpCodes)
+ {
+ GetBlock(ssyOp.GetAbsoluteAddress());
+ }
+
+ //Set child blocks. "Branch" is the block the branch instruction
+ //points to (when taken), "Next" is the block at the next address,
+ //executed when the branch is not taken. For Unconditional Branches
+ //or end of program, Next is null.
+ OpCode lastOp = currBlock.GetLastOp();
+
+ if (lastOp is OpCodeBranch op)
+ {
+ currBlock.Branch = GetBlock(op.GetAbsoluteAddress());
+ }
+
+ if (!IsUnconditionalBranch(lastOp))
+ {
+ currBlock.Next = GetBlock(currBlock.EndAddress);
+ }
+ }
+
+ //Insert the new block on the list (sorted by address).
+ if (blocks.Count != 0)
+ {
+ Block nBlock = blocks[nBlkIndex];
+
+ blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock);
+ }
+ else
+ {
+ blocks.Add(currBlock);
+ }
+ }
+
+ foreach (Block ssyBlock in blocks.Where(x => x.SsyOpCodes.Count != 0))
+ {
+ for (int ssyIndex = 0; ssyIndex < ssyBlock.SsyOpCodes.Count; ssyIndex++)
+ {
+ PropagateSsy(visited, ssyBlock, ssyIndex);
+ }
+ }
+
+ return blocks.ToArray();
+ }
+
+ private static bool BinarySearch(List<Block> blocks, ulong address, out int index)
+ {
+ index = 0;
+
+ int left = 0;
+ int right = blocks.Count - 1;
+
+ while (left <= right)
+ {
+ int size = right - left;
+
+ int middle = left + (size >> 1);
+
+ Block block = blocks[middle];
+
+ index = middle;
+
+ if (address >= block.Address && address < block.EndAddress)
+ {
+ return true;
+ }
+
+ if (address < block.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return false;
+ }
+
+ private static void FillBlock(
+ IGalMemory memory,
+ Block block,
+ ulong limitAddress,
+ ulong startAddress)
+ {
+ ulong address = block.Address;
+
+ do
+ {
+ if (address >= limitAddress)
+ {
+ break;
+ }
+
+ //Ignore scheduling instructions, which are written every 32 bytes.
+ if (((address - startAddress) & 0x1f) == 0)
+ {
+ address += 8;
+
+ continue;
+ }
+
+ uint word0 = (uint)memory.ReadInt32((long)(address + 0));
+ uint word1 = (uint)memory.ReadInt32((long)(address + 4));
+
+ ulong opAddress = address;
+
+ address += 8;
+
+ long opCode = word0 | (long)word1 << 32;
+
+ (InstEmitter emitter, Type opCodeType) = OpCodeTable.GetEmitter(opCode);
+
+ if (emitter == null)
+ {
+ //TODO: Warning, illegal encoding.
+ continue;
+ }
+
+ OpCode op = MakeOpCode(opCodeType, emitter, opAddress, opCode);
+
+ block.OpCodes.Add(op);
+ }
+ while (!IsBranch(block.GetLastOp()));
+
+ block.EndAddress = address;
+
+ block.UpdateSsyOpCodes();
+ }
+
+ private static bool IsUnconditionalBranch(OpCode opCode)
+ {
+ return IsUnconditional(opCode) && IsBranch(opCode);
+ }
+
+ private static bool IsUnconditional(OpCode opCode)
+ {
+ if (opCode is OpCodeExit op && op.Condition != Condition.Always)
+ {
+ return false;
+ }
+
+ return opCode.Predicate.Index == RegisterConsts.PredicateTrueIndex && !opCode.InvertPredicate;
+ }
+
+ private static bool IsBranch(OpCode opCode)
+ {
+ return (opCode is OpCodeBranch && opCode.Emitter != InstEmit.Ssy) ||
+ opCode is OpCodeSync ||
+ opCode is OpCodeExit;
+ }
+
+ private static OpCode MakeOpCode(Type type, InstEmitter emitter, ulong address, long opCode)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ OpActivator createInstance = _opActivators.GetOrAdd(type, CacheOpActivator);
+
+ return (OpCode)createInstance(emitter, address, opCode);
+ }
+
+ private static OpActivator CacheOpActivator(Type type)
+ {
+ Type[] argTypes = new Type[] { typeof(InstEmitter), typeof(ulong), typeof(long) };
+
+ DynamicMethod mthd = new DynamicMethod($"Make{type.Name}", type, argTypes);
+
+ ILGenerator generator = mthd.GetILGenerator();
+
+ generator.Emit(OpCodes.Ldarg_0);
+ generator.Emit(OpCodes.Ldarg_1);
+ generator.Emit(OpCodes.Ldarg_2);
+ generator.Emit(OpCodes.Newobj, type.GetConstructor(argTypes));
+ generator.Emit(OpCodes.Ret);
+
+ return (OpActivator)mthd.CreateDelegate(typeof(OpActivator));
+ }
+
+ private struct PathBlockState
+ {
+ public Block Block { get; }
+
+ private enum RestoreType
+ {
+ None,
+ PopSsy,
+ PushSync
+ }
+
+ private RestoreType _restoreType;
+
+ private ulong _restoreValue;
+
+ public bool ReturningFromVisit => _restoreType != RestoreType.None;
+
+ public PathBlockState(Block block)
+ {
+ Block = block;
+ _restoreType = RestoreType.None;
+ _restoreValue = 0;
+ }
+
+ public PathBlockState(int oldSsyStackSize)
+ {
+ Block = null;
+ _restoreType = RestoreType.PopSsy;
+ _restoreValue = (ulong)oldSsyStackSize;
+ }
+
+ public PathBlockState(ulong syncAddress)
+ {
+ Block = null;
+ _restoreType = RestoreType.PushSync;
+ _restoreValue = syncAddress;
+ }
+
+ public void RestoreStackState(Stack<ulong> 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<ulong, Block> blocks, Block ssyBlock, int ssyIndex)
+ {
+ OpCodeSsy ssyOp = ssyBlock.SsyOpCodes[ssyIndex];
+
+ Stack<PathBlockState> workQueue = new Stack<PathBlockState>();
+
+ HashSet<Block> visited = new HashSet<Block>();
+
+ Stack<ulong> ssyStack = new Stack<ulong>();
+
+ void Push(PathBlockState pbs)
+ {
+ if (pbs.Block == null || visited.Add(pbs.Block))
+ {
+ workQueue.Push(pbs);
+ }
+ }
+
+ Push(new PathBlockState(ssyBlock));
+
+ while (workQueue.TryPop(out PathBlockState pbs))
+ {
+ if (pbs.ReturningFromVisit)
+ {
+ pbs.RestoreStackState(ssyStack);
+
+ continue;
+ }
+
+ Block current = pbs.Block;
+
+ int ssyOpCodesCount = current.SsyOpCodes.Count;
+
+ if (ssyOpCodesCount != 0)
+ {
+ Push(new PathBlockState(ssyStack.Count));
+
+ for (int index = ssyIndex; index < ssyOpCodesCount; index++)
+ {
+ ssyStack.Push(current.SsyOpCodes[index].GetAbsoluteAddress());
+ }
+ }
+
+ ssyIndex = 0;
+
+ if (current.Next != null)
+ {
+ Push(new PathBlockState(current.Next));
+ }
+
+ if (current.Branch != null)
+ {
+ Push(new PathBlockState(current.Branch));
+ }
+ else if (current.GetLastOp() is OpCodeSync op)
+ {
+ ulong syncAddress = ssyStack.Pop();
+
+ if (ssyStack.Count == 0)
+ {
+ ssyStack.Push(syncAddress);
+
+ op.Targets.Add(ssyOp, op.Targets.Count);
+
+ ssyOp.Syncs.TryAdd(op, Local());
+ }
+ else
+ {
+ Push(new PathBlockState(syncAddress));
+ Push(new PathBlockState(blocks[syncAddress]));
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs b/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs
new file mode 100644
index 00000000..fd0a45e8
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ static class DecoderHelper
+ {
+ public static int DecodeS20Immediate(long opCode)
+ {
+ int imm = opCode.Extract(20, 19);
+
+ bool negate = opCode.Extract(56);
+
+ if (negate)
+ {
+ imm = -imm;
+ }
+
+ return imm;
+ }
+
+ public static int Decode2xF10Immediate(long opCode)
+ {
+ int immH0 = opCode.Extract(20, 9);
+ int immH1 = opCode.Extract(30, 9);
+
+ bool negateH0 = opCode.Extract(29);
+ bool negateH1 = opCode.Extract(56);
+
+ if (negateH0)
+ {
+ immH0 |= 1 << 9;
+ }
+
+ if (negateH1)
+ {
+ immH1 |= 1 << 9;
+ }
+
+ return immH1 << 22 | immH0 << 6;
+ }
+
+ public static float DecodeF20Immediate(long opCode)
+ {
+ int imm = opCode.Extract(20, 19);
+
+ bool negate = opCode.Extract(56);
+
+ imm <<= 12;
+
+ if (negate)
+ {
+ imm |= 1 << 31;
+ }
+
+ return BitConverter.Int32BitsToSingle(imm);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs b/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs
new file mode 100644
index 00000000..3ddf17cf
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum FPHalfSwizzle
+ {
+ FP16 = 0,
+ FP32 = 1,
+ DupH0 = 2,
+ DupH1 = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/FPType.cs b/Ryujinx.Graphics/Shader/Decoders/FPType.cs
new file mode 100644
index 00000000..e602ad45
--- /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 00000000..c35c6e48
--- /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 00000000..dd6ad79a
--- /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 00000000..d840d49d
--- /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 00000000..42a17451
--- /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 00000000..d68ccf59
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ interface IOpCodeFArith : IOpCodeAlu
+ {
+ RoundingMode RoundingMode { get; }
+
+ FmulScale Scale { get; }
+
+ bool FlushToZero { get; }
+ bool AbsoluteA { get; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs
new file mode 100644
index 00000000..4638f660
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ interface IOpCodeHfma : IOpCode
+ {
+ bool NegateB { get; }
+ bool NegateC { get; }
+ bool Saturate { get; }
+
+ FPHalfSwizzle SwizzleA { get; }
+ FPHalfSwizzle SwizzleB { get; }
+ FPHalfSwizzle SwizzleC { get; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs b/Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs
new file mode 100644
index 00000000..9cfcd69b
--- /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 00000000..629eff79
--- /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 00000000..62c87bf4
--- /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 00000000..e5902110
--- /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 00000000..bb806b95
--- /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 00000000..099c4061
--- /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 00000000..3ed157e8
--- /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 00000000..429f01bb
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ interface IOpCodeRegCbuf : IOpCodeRc
+ {
+ int Offset { get; }
+ int Slot { get; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs
new file mode 100644
index 00000000..a1937c2f
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum IntegerCondition
+ {
+ Less = 1 << 0,
+ Equal = 1 << 1,
+ Greater = 1 << 2,
+
+ Never = 0,
+
+ LessOrEqual = Less | Equal,
+ NotEqual = Less | Greater,
+ GreaterOrEqual = Greater | Equal,
+ Number = Greater | Equal | Less,
+
+ Always = 7
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs
new file mode 100644
index 00000000..b779f44d
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum IntegerHalfPart
+ {
+ B32 = 0,
+ H0 = 1,
+ H1 = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs
new file mode 100644
index 00000000..ce4d9f3b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum IntegerShift
+ {
+ NoShift = 0,
+ ShiftRight = 1,
+ ShiftLeft = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs
new file mode 100644
index 00000000..70fdfc3d
--- /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 00000000..46734dbe
--- /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 00000000..52214425
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum LogicalOperation
+ {
+ And = 0,
+ Or = 1,
+ ExclusiveOr = 2,
+ Passthrough = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs b/Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs
new file mode 100644
index 00000000..88bd1f5c
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum MufuOperation
+ {
+ Cosine = 0,
+ Sine = 1,
+ ExponentB2 = 2,
+ LogarithmB2 = 3,
+ Reciprocal = 4,
+ ReciprocalSquareRoot = 5,
+ Reciprocal64H = 6,
+ ReciprocalSquareRoot64H = 7,
+ SquareRoot = 8
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCode.cs b/Ryujinx.Graphics/Shader/Decoders/OpCode.cs
new file mode 100644
index 00000000..b0f2ffdc
--- /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 00000000..15fbb9af
--- /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 00000000..9c127989
--- /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 00000000..a407fc6b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeAluImm : OpCodeAlu, IOpCodeImm
+ {
+ public int Immediate { get; }
+
+ public OpCodeAluImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Immediate = DecoderHelper.DecodeS20Immediate(opCode);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs
new file mode 100644
index 00000000..9aeb32bd
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeAluImm2x10 : OpCodeAlu, IOpCodeImm
+ {
+ public int Immediate { get; }
+
+ public OpCodeAluImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Immediate = DecoderHelper.Decode2xF10Immediate(opCode);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs
new file mode 100644
index 00000000..5941e0b9
--- /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 00000000..13b96a3a
--- /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 00000000..6cf6bd2e
--- /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 00000000..fd8e63fc
--- /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 00000000..25941b39
--- /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 00000000..d50903eb
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeExit : OpCode
+ {
+ public Condition Condition { get; }
+
+ public OpCodeExit(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Condition = (Condition)opCode.Extract(0, 5);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs
new file mode 100644
index 00000000..c88f7f0e
--- /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 00000000..5486bb0b
--- /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 00000000..1bb6f425
--- /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 00000000..ec9da6f3
--- /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 00000000..55cf4485
--- /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 00000000..315c2c8b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeFArithRegCbuf : OpCodeFArith, IOpCodeRegCbuf
+ {
+ public int Offset { get; }
+ public int Slot { get; }
+
+ public OpCodeFArithRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Offset = opCode.Extract(20, 14);
+ Slot = opCode.Extract(34, 5);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs
new file mode 100644
index 00000000..cb5f155e
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeFsetImm : OpCodeSet, IOpCodeImmF
+ {
+ public float Immediate { get; }
+
+ public OpCodeFsetImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Immediate = DecoderHelper.DecodeF20Immediate(opCode);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs
new file mode 100644
index 00000000..32f3cd7a
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs
@@ -0,0 +1,22 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeHfma : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeRc
+ {
+ public Register Rd { get; }
+ public Register Ra { get; }
+ public Register Rc { get; protected set; }
+
+ public FPHalfSwizzle SwizzleA { get; }
+
+ public OpCodeHfma(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr);
+ Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr);
+ Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr);
+
+ SwizzleA = (FPHalfSwizzle)opCode.Extract(47, 2);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs
new file mode 100644
index 00000000..33768c7d
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs
@@ -0,0 +1,30 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeHfmaCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeCbuf
+ {
+ public int Offset { get; }
+ public int Slot { get; }
+
+ public bool NegateB { get; }
+ public bool NegateC { get; }
+ public bool Saturate { get; }
+
+ public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP32;
+ public FPHalfSwizzle SwizzleC { get; }
+
+ public OpCodeHfmaCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Offset = opCode.Extract(20, 14);
+ Slot = opCode.Extract(34, 5);
+
+ NegateC = opCode.Extract(51);
+ Saturate = opCode.Extract(52);
+
+ SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2);
+
+ NegateB = opCode.Extract(56);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs
new file mode 100644
index 00000000..80a5a140
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeHfmaImm2x10 : OpCodeHfma, IOpCodeHfma, IOpCodeImm
+ {
+ public int Immediate { get; }
+
+ public bool NegateB => false;
+ public bool NegateC { get; }
+ public bool Saturate { get; }
+
+ public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16;
+ public FPHalfSwizzle SwizzleC { get; }
+
+ public OpCodeHfmaImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Immediate = DecoderHelper.Decode2xF10Immediate(opCode);
+
+ NegateC = opCode.Extract(51);
+ Saturate = opCode.Extract(52);
+
+ SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs
new file mode 100644
index 00000000..05eb9ffe
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeHfmaImm32 : OpCodeHfma, IOpCodeHfma, IOpCodeImm
+ {
+ public int Immediate { get; }
+
+ public bool NegateB => false;
+ public bool NegateC { get; }
+ public bool Saturate => false;
+
+ public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16;
+ public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP16;
+
+ public OpCodeHfmaImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Immediate = opCode.Extract(20, 32);
+
+ NegateC = opCode.Extract(52);
+
+ Rc = Rd;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs
new file mode 100644
index 00000000..714c89de
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeHfmaReg : OpCodeHfma, IOpCodeHfma, IOpCodeReg
+ {
+ public Register Rb { get; }
+
+ public bool NegateB { get; }
+ public bool NegateC { get; }
+ public bool Saturate { get; }
+
+ public FPHalfSwizzle SwizzleB { get; }
+ public FPHalfSwizzle SwizzleC { get; }
+
+ public OpCodeHfmaReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr);
+
+ SwizzleB = (FPHalfSwizzle)opCode.Extract(28, 2);
+
+ NegateC = opCode.Extract(30);
+ NegateB = opCode.Extract(31);
+ Saturate = opCode.Extract(32);
+
+ SwizzleC = (FPHalfSwizzle)opCode.Extract(35, 2);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs
new file mode 100644
index 00000000..c0001908
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs
@@ -0,0 +1,30 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeHfmaRegCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeRegCbuf
+ {
+ public int Offset { get; }
+ public int Slot { get; }
+
+ public bool NegateB { get; }
+ public bool NegateC { get; }
+ public bool Saturate { get; }
+
+ public FPHalfSwizzle SwizzleB { get; }
+ public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP32;
+
+ public OpCodeHfmaRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Offset = opCode.Extract(20, 14);
+ Slot = opCode.Extract(34, 5);
+
+ NegateC = opCode.Extract(51);
+ Saturate = opCode.Extract(52);
+
+ SwizzleB = (FPHalfSwizzle)opCode.Extract(53, 2);
+
+ NegateB = opCode.Extract(56);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs
new file mode 100644
index 00000000..e21095a3
--- /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 00000000..cc9f0658
--- /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 00000000..c5f90345
--- /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 00000000..f174733c
--- /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 00000000..a2f091a2
--- /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 00000000..cb48f3a6
--- /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 00000000..5f43db72
--- /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 00000000..729e3207
--- /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 00000000..cd6773a1
--- /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 00000000..4f3dbd74
--- /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 00000000..bc63b9f4
--- /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 00000000..bbdee196
--- /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 00000000..499c0706
--- /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<OpCodeSync, Operand> Syncs { get; }
+
+ public OpCodeSsy(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Syncs = new Dictionary<OpCodeSync, Operand>();
+
+ 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 00000000..081d08a0
--- /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<OpCodeSsy, int> Targets { get; }
+
+ public OpCodeSync(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Targets = new Dictionary<OpCodeSsy, int>();
+ }
+ }
+} \ 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 00000000..d588ce8e
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs
@@ -0,0 +1,216 @@
+using Ryujinx.Graphics.Shader.Instructions;
+using System;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ static class OpCodeTable
+ {
+ private const int EncodingBits = 14;
+
+ private class TableEntry
+ {
+ public InstEmitter Emitter { get; }
+
+ public Type OpCodeType { get; }
+
+ public int XBits { get; }
+
+ public TableEntry(InstEmitter emitter, Type opCodeType, int xBits)
+ {
+ Emitter = emitter;
+ OpCodeType = opCodeType;
+ XBits = xBits;
+ }
+ }
+
+ private static TableEntry[] _opCodes;
+
+ static OpCodeTable()
+ {
+ _opCodes = new TableEntry[1 << EncodingBits];
+
+#region Instructions
+ Set("1110111111011x", InstEmit.Ald, typeof(OpCodeAttribute));
+ Set("1110111111110x", InstEmit.Ast, typeof(OpCodeAttribute));
+ Set("0100110000000x", InstEmit.Bfe, typeof(OpCodeAluCbuf));
+ Set("0011100x00000x", InstEmit.Bfe, typeof(OpCodeAluImm));
+ Set("0101110000000x", InstEmit.Bfe, typeof(OpCodeAluReg));
+ Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch));
+ Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit));
+ Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf));
+ Set("0011100x10101x", InstEmit.F2F, typeof(OpCodeFArithImm));
+ Set("0101110010101x", InstEmit.F2F, typeof(OpCodeFArithReg));
+ Set("0100110010110x", InstEmit.F2I, typeof(OpCodeFArithCbuf));
+ Set("0011100x10110x", InstEmit.F2I, typeof(OpCodeFArithImm));
+ Set("0101110010110x", InstEmit.F2I, typeof(OpCodeFArithReg));
+ Set("0100110001011x", InstEmit.Fadd, typeof(OpCodeFArithCbuf));
+ Set("0011100x01011x", InstEmit.Fadd, typeof(OpCodeFArithImm));
+ Set("000010xxxxxxxx", InstEmit.Fadd, typeof(OpCodeFArithImm32));
+ Set("0101110001011x", InstEmit.Fadd, typeof(OpCodeFArithReg));
+ Set("010010011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithCbuf));
+ Set("0011001x1xxxxx", InstEmit.Ffma, typeof(OpCodeFArithImm));
+ Set("010100011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithRegCbuf));
+ Set("010110011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithReg));
+ Set("0100110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithCbuf));
+ Set("0011100x01100x", InstEmit.Fmnmx, typeof(OpCodeFArithImm));
+ Set("0101110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithReg));
+ Set("0100110001101x", InstEmit.Fmul, typeof(OpCodeFArithCbuf));
+ Set("0011100x01101x", InstEmit.Fmul, typeof(OpCodeFArithImm));
+ Set("00011110xxxxxx", InstEmit.Fmul, typeof(OpCodeFArithImm32));
+ Set("0101110001101x", InstEmit.Fmul, typeof(OpCodeFArithReg));
+ Set("0100100xxxxxxx", InstEmit.Fset, typeof(OpCodeSetCbuf));
+ Set("0011000xxxxxxx", InstEmit.Fset, typeof(OpCodeFsetImm));
+ Set("01011000xxxxxx", InstEmit.Fset, typeof(OpCodeSetReg));
+ Set("010010111011xx", InstEmit.Fsetp, typeof(OpCodeSetCbuf));
+ Set("0011011x1011xx", InstEmit.Fsetp, typeof(OpCodeFsetImm));
+ Set("010110111011xx", InstEmit.Fsetp, typeof(OpCodeSetReg));
+ Set("0111101x1xxxxx", InstEmit.Hadd2, typeof(OpCodeAluCbuf));
+ Set("0111101x0xxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm2x10));
+ Set("0010110xxxxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm32));
+ Set("0101110100010x", InstEmit.Hadd2, typeof(OpCodeAluReg));
+ Set("01110xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaCbuf));
+ Set("01110xxx0xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm2x10));
+ Set("0010100xxxxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm32));
+ Set("0101110100000x", InstEmit.Hfma2, typeof(OpCodeHfmaReg));
+ Set("01100xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaRegCbuf));
+ Set("0111100x1xxxxx", InstEmit.Hmul2, typeof(OpCodeAluCbuf));
+ Set("0111100x0xxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm2x10));
+ Set("0010101xxxxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm32));
+ Set("0101110100001x", InstEmit.Hmul2, typeof(OpCodeAluReg));
+ Set("0100110010111x", InstEmit.I2F, typeof(OpCodeAluCbuf));
+ Set("0011100x10111x", InstEmit.I2F, typeof(OpCodeAluImm));
+ Set("0101110010111x", InstEmit.I2F, typeof(OpCodeAluReg));
+ Set("0100110011100x", InstEmit.I2I, typeof(OpCodeAluCbuf));
+ Set("0011100x11100x", InstEmit.I2I, typeof(OpCodeAluImm));
+ Set("0101110011100x", InstEmit.I2I, typeof(OpCodeAluReg));
+ Set("0100110000010x", InstEmit.Iadd, typeof(OpCodeAluCbuf));
+ Set("0011100000010x", InstEmit.Iadd, typeof(OpCodeAluImm));
+ Set("0001110x0xxxxx", InstEmit.Iadd, typeof(OpCodeAluImm32));
+ Set("0101110000010x", InstEmit.Iadd, typeof(OpCodeAluReg));
+ Set("010011001100xx", InstEmit.Iadd3, typeof(OpCodeAluCbuf));
+ Set("001110001100xx", InstEmit.Iadd3, typeof(OpCodeAluImm));
+ Set("010111001100xx", InstEmit.Iadd3, typeof(OpCodeAluReg));
+ Set("0100110000100x", InstEmit.Imnmx, typeof(OpCodeAluCbuf));
+ Set("0011100x00100x", InstEmit.Imnmx, typeof(OpCodeAluImm));
+ Set("0101110000100x", InstEmit.Imnmx, typeof(OpCodeAluReg));
+ Set("11100000xxxxxx", InstEmit.Ipa, typeof(OpCodeIpa));
+ Set("0100110000011x", InstEmit.Iscadd, typeof(OpCodeAluCbuf));
+ Set("0011100x00011x", InstEmit.Iscadd, typeof(OpCodeAluImm));
+ Set("000101xxxxxxxx", InstEmit.Iscadd, typeof(OpCodeAluImm32));
+ Set("0101110000011x", InstEmit.Iscadd, typeof(OpCodeAluReg));
+ Set("010010110101xx", InstEmit.Iset, typeof(OpCodeSetCbuf));
+ Set("001101100101xx", InstEmit.Iset, typeof(OpCodeSetImm));
+ Set("010110110101xx", InstEmit.Iset, typeof(OpCodeSetReg));
+ Set("010010110110xx", InstEmit.Isetp, typeof(OpCodeSetCbuf));
+ Set("0011011x0110xx", InstEmit.Isetp, typeof(OpCodeSetImm));
+ Set("010110110110xx", InstEmit.Isetp, typeof(OpCodeSetReg));
+ Set("111000110011xx", InstEmit.Kil, typeof(OpCodeExit));
+ Set("1110111110010x", InstEmit.Ldc, typeof(OpCodeLdc));
+ Set("0100110001000x", InstEmit.Lop, typeof(OpCodeLopCbuf));
+ Set("0011100001000x", InstEmit.Lop, typeof(OpCodeLopImm));
+ Set("000001xxxxxxxx", InstEmit.Lop, typeof(OpCodeLopImm32));
+ Set("0101110001000x", InstEmit.Lop, typeof(OpCodeLopReg));
+ Set("0010000xxxxxxx", InstEmit.Lop3, typeof(OpCodeLopCbuf));
+ Set("001111xxxxxxxx", InstEmit.Lop3, typeof(OpCodeLopImm));
+ Set("0101101111100x", InstEmit.Lop3, typeof(OpCodeLopReg));
+ Set("0100110010011x", InstEmit.Mov, typeof(OpCodeAluCbuf));
+ Set("0011100x10011x", InstEmit.Mov, typeof(OpCodeAluImm));
+ Set("000000010000xx", InstEmit.Mov, typeof(OpCodeAluImm32));
+ Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg));
+ Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith));
+ Set("1111101111100x", InstEmit.Out, typeof(OpCode));
+ Set("0101000010010x", InstEmit.Psetp, typeof(OpCodePsetp));
+ Set("0100110010010x", InstEmit.Rro, typeof(OpCodeFArithCbuf));
+ Set("0011100x10010x", InstEmit.Rro, typeof(OpCodeFArithImm));
+ Set("0101110010010x", InstEmit.Rro, typeof(OpCodeFArithReg));
+ Set("0100110010100x", InstEmit.Sel, typeof(OpCodeAluCbuf));
+ Set("0011100010100x", InstEmit.Sel, typeof(OpCodeAluImm));
+ Set("0101110010100x", InstEmit.Sel, typeof(OpCodeAluReg));
+ Set("0100110001001x", InstEmit.Shl, typeof(OpCodeAluCbuf));
+ Set("0011100x01001x", InstEmit.Shl, typeof(OpCodeAluImm));
+ Set("0101110001001x", InstEmit.Shl, typeof(OpCodeAluReg));
+ Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf));
+ Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm));
+ Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg));
+ Set("111000101001xx", InstEmit.Ssy, typeof(OpCodeSsy));
+ Set("1111000011111x", InstEmit.Sync, typeof(OpCodeSync));
+ Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex));
+ Set("1101111010111x", InstEmit.Tex_B, typeof(OpCodeTex));
+ Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs));
+ Set("1101x01xxxxxxx", InstEmit.Texs, typeof(OpCodeTlds));
+ Set("1101x11100xxxx", InstEmit.Texs, typeof(OpCodeTld4s));
+ Set("11011100xx111x", InstEmit.Tld, typeof(OpCodeTld));
+ Set("11011101xx111x", InstEmit.Tld_B, typeof(OpCodeTld));
+ Set("110010xxxx111x", InstEmit.Tld4, typeof(OpCodeTld4));
+ Set("1101111101001x", InstEmit.Txq, typeof(OpCodeTex));
+ Set("1101111101010x", InstEmit.Txq_B, typeof(OpCodeTex));
+ Set("0100111xxxxxxx", InstEmit.Xmad, typeof(OpCodeAluCbuf));
+ Set("0011011x00xxxx", InstEmit.Xmad, typeof(OpCodeAluImm));
+ Set("010100010xxxxx", InstEmit.Xmad, typeof(OpCodeAluRegCbuf));
+ Set("0101101100xxxx", InstEmit.Xmad, typeof(OpCodeAluReg));
+#endregion
+ }
+
+ private static void Set(string encoding, InstEmitter emitter, Type opCodeType)
+ {
+ if (encoding.Length != EncodingBits)
+ {
+ throw new ArgumentException(nameof(encoding));
+ }
+
+ int bit = encoding.Length - 1;
+ int value = 0;
+ int xMask = 0;
+ int xBits = 0;
+
+ int[] xPos = new int[encoding.Length];
+
+ for (int index = 0; index < encoding.Length; index++, bit--)
+ {
+ char chr = encoding[index];
+
+ if (chr == '1')
+ {
+ value |= 1 << bit;
+ }
+ else if (chr == 'x')
+ {
+ xMask |= 1 << bit;
+
+ xPos[xBits++] = bit;
+ }
+ }
+
+ xMask = ~xMask;
+
+ TableEntry entry = new TableEntry(emitter, opCodeType, xBits);
+
+ for (int index = 0; index < (1 << xBits); index++)
+ {
+ value &= xMask;
+
+ for (int X = 0; X < xBits; X++)
+ {
+ value |= ((index >> X) & 1) << xPos[X];
+ }
+
+ if (_opCodes[value] == null || _opCodes[value].XBits > xBits)
+ {
+ _opCodes[value] = entry;
+ }
+ }
+ }
+
+ public static (InstEmitter emitter, Type opCodeType) GetEmitter(long OpCode)
+ {
+ TableEntry entry = _opCodes[(ulong)OpCode >> (64 - EncodingBits)];
+
+ if (entry != null)
+ {
+ return (entry.Emitter, entry.OpCodeType);
+ }
+
+ return (null, null);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs
new file mode 100644
index 00000000..da8756b9
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTex : OpCodeTexture
+ {
+ public OpCodeTex(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ HasDepthCompare = opCode.Extract(50);
+
+ HasOffset = opCode.Extract(54);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs
new file mode 100644
index 00000000..0822c4c0
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs
@@ -0,0 +1,11 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTexs : OpCodeTextureScalar
+ {
+ public TextureScalarType Type => (TextureScalarType)RawType;
+
+ public OpCodeTexs(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs
new file mode 100644
index 00000000..7a7e8f46
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs
@@ -0,0 +1,42 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTexture : OpCode
+ {
+ public Register Rd { get; }
+ public Register Ra { get; }
+ public Register Rb { get; }
+
+ public bool IsArray { get; }
+
+ public TextureDimensions Dimensions { get; }
+
+ public int ComponentMask { get; }
+
+ public int Immediate { get; }
+
+ public TextureLodMode LodMode { get; protected set; }
+
+ public bool HasOffset { get; protected set; }
+ public bool HasDepthCompare { get; protected set; }
+ public bool IsMultisample { get; protected set; }
+
+ public OpCodeTexture(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr);
+ Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr);
+ Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr);
+
+ IsArray = opCode.Extract(28);
+
+ Dimensions = (TextureDimensions)opCode.Extract(29, 2);
+
+ ComponentMask = opCode.Extract(31, 4);
+
+ Immediate = opCode.Extract(36, 13);
+
+ LodMode = (TextureLodMode)opCode.Extract(55, 3);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs
new file mode 100644
index 00000000..4389f453
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs
@@ -0,0 +1,61 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTextureScalar : OpCode
+ {
+#region "Component mask LUT"
+ private const int ____ = 0x0;
+ private const int R___ = 0x1;
+ private const int _G__ = 0x2;
+ private const int RG__ = 0x3;
+ private const int __B_ = 0x4;
+ private const int RGB_ = 0x7;
+ private const int ___A = 0x8;
+ private const int R__A = 0x9;
+ private const int _G_A = 0xa;
+ private const int RG_A = 0xb;
+ private const int __BA = 0xc;
+ private const int R_BA = 0xd;
+ private const int _GBA = 0xe;
+ private const int RGBA = 0xf;
+
+ private static int[,] _maskLut = new int[,]
+ {
+ { R___, _G__, __B_, ___A, RG__, R__A, _G_A, __BA },
+ { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ }
+ };
+#endregion
+
+ public Register Rd0 { get; }
+ public Register Ra { get; }
+ public Register Rb { get; }
+ public Register Rd1 { get; }
+
+ public int Immediate { get; }
+
+ public int ComponentMask { get; }
+
+ protected int RawType;
+
+ public bool IsFp16 { get; }
+
+ public OpCodeTextureScalar(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ Rd0 = new Register(opCode.Extract(0, 8), RegisterType.Gpr);
+ Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr);
+ Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr);
+ Rd1 = new Register(opCode.Extract(28, 8), RegisterType.Gpr);
+
+ Immediate = opCode.Extract(36, 13);
+
+ int compSel = opCode.Extract(50, 3);
+
+ RawType = opCode.Extract(53, 4);
+
+ IsFp16 = !opCode.Extract(59);
+
+ ComponentMask = _maskLut[Rd1.IsRZ ? 0 : 1, compSel];
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs
new file mode 100644
index 00000000..61bd900b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTld : OpCodeTexture
+ {
+ public OpCodeTld(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ HasOffset = opCode.Extract(35);
+
+ IsMultisample = opCode.Extract(50);
+
+ bool isLL = opCode.Extract(55);
+
+ LodMode = isLL
+ ? TextureLodMode.LodLevel
+ : TextureLodMode.LodZero;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs
new file mode 100644
index 00000000..485edf93
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTld4 : OpCodeTexture
+ {
+ public TextureGatherOffset Offset { get; }
+
+ public int GatherCompIndex { get; }
+
+ public OpCodeTld4(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ HasDepthCompare = opCode.Extract(50);
+
+ Offset = (TextureGatherOffset)opCode.Extract(54, 2);
+
+ GatherCompIndex = opCode.Extract(56, 2);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs
new file mode 100644
index 00000000..0d7b8460
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTld4s : OpCodeTextureScalar
+ {
+ public bool HasDepthCompare { get; }
+ public bool HasOffset { get; }
+
+ public int GatherCompIndex { get; }
+
+ public OpCodeTld4s(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ HasDepthCompare = opCode.Extract(50);
+ HasOffset = opCode.Extract(51);
+
+ GatherCompIndex = opCode.Extract(52, 2);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs
new file mode 100644
index 00000000..e117721e
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs
@@ -0,0 +1,11 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTlds : OpCodeTextureScalar
+ {
+ public TexelLoadScalarType Type => (TexelLoadScalarType)RawType;
+
+ public OpCodeTlds(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/Register.cs b/Ryujinx.Graphics/Shader/Decoders/Register.cs
new file mode 100644
index 00000000..30840d8c
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/Register.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ struct Register : IEquatable<Register>
+ {
+ 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 00000000..d381f954
--- /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 00000000..648f816a
--- /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 00000000..13bb08dc
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum RoundingMode
+ {
+ ToNearest = 0,
+ TowardsNegativeInfinity = 1,
+ TowardsPositiveInfinity = 2,
+ TowardsZero = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs b/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs
new file mode 100644
index 00000000..cef5778a
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum TexelLoadScalarType
+ {
+ Texture1DLodZero = 0x0,
+ Texture1DLodLevel = 0x1,
+ Texture2DLodZero = 0x2,
+ Texture2DLodZeroOffset = 0x4,
+ Texture2DLodLevel = 0x5,
+ Texture2DLodZeroMultisample = 0x6,
+ Texture3DLodZero = 0x7,
+ Texture2DArrayLodZero = 0x8,
+ Texture2DLodLevelOffset = 0xc
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs b/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs
new file mode 100644
index 00000000..dbdf1927
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum TextureDimensions
+ {
+ Texture1D = 0,
+ Texture2D = 1,
+ Texture3D = 2,
+ TextureCube = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs b/Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs
new file mode 100644
index 00000000..4e9ade26
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum TextureGatherOffset
+ {
+ None = 0,
+ Offset = 1,
+ Offsets = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs b/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs
new file mode 100644
index 00000000..0cc6f714
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum TextureLodMode
+ {
+ None = 0,
+ LodZero = 1,
+ LodBias = 2,
+ LodLevel = 3,
+ LodBiasA = 4, //?
+ LodLevelA = 5 //?
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs b/Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs
new file mode 100644
index 00000000..ea35b1d1
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum TextureProperty
+ {
+ Dimensions = 0x1,
+ Type = 0x2,
+ SamplePos = 0x5,
+ Filter = 0xa,
+ Lod = 0xc,
+ Wrap = 0xe,
+ BorderColor = 0x10
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs b/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs
new file mode 100644
index 00000000..0055174b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum TextureScalarType
+ {
+ Texture1DLodZero = 0x0,
+ Texture2D = 0x1,
+ Texture2DLodZero = 0x2,
+ Texture2DLodLevel = 0x3,
+ Texture2DDepthCompare = 0x4,
+ Texture2DLodLevelDepthCompare = 0x5,
+ Texture2DLodZeroDepthCompare = 0x6,
+ Texture2DArray = 0x7,
+ Texture2DArrayLodZero = 0x8,
+ Texture2DArrayLodZeroDepthCompare = 0x9,
+ Texture3D = 0xa,
+ Texture3DLodZero = 0xb,
+ TextureCube = 0xc,
+ TextureCubeLodLevel = 0xd
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs b/Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs
new file mode 100644
index 00000000..949a2ef7
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ enum XmadCMode
+ {
+ Cfull = 0,
+ Clo = 1,
+ Chi = 2,
+ Csfu = 3,
+ Cbcc = 4
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs
new file mode 100644
index 00000000..f7815e23
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs
@@ -0,0 +1,684 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Bfe(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isReverse = op.RawOpCode.Extract(40);
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ if (isReverse)
+ {
+ srcA = context.BitfieldReverse(srcA);
+ }
+
+ Operand position = context.BitwiseAnd(srcB, Const(0xff));
+
+ Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8));
+
+ Operand res = isSigned
+ ? context.BitfieldExtractS32(srcA, position, size)
+ : context.BitfieldExtractU32(srcA, position, size);
+
+ context.Copy(GetDest(context), res);
+
+ //TODO: CC, X, corner cases
+ }
+
+ public static void Iadd(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool negateA = false, negateB = false;
+
+ if (!(op is OpCodeAluImm32))
+ {
+ negateB = op.RawOpCode.Extract(48);
+ negateA = op.RawOpCode.Extract(49);
+ }
+
+ Operand srcA = context.INegate(GetSrcA(context), negateA);
+ Operand srcB = context.INegate(GetSrcB(context), negateB);
+
+ Operand res = context.IAdd(srcA, srcB);
+
+ bool isSubtraction = negateA || negateB;
+
+ if (op.Extended)
+ {
+ //Add carry, or subtract borrow.
+ res = context.IAdd(res, isSubtraction
+ ? context.BitwiseNot(GetCF(context))
+ : context.BitwiseAnd(GetCF(context), Const(1)));
+ }
+
+ SetIaddFlags(context, res, srcA, srcB, op.SetCondCode, op.Extended, isSubtraction);
+
+ context.Copy(GetDest(context), res);
+ }
+
+ public static void Iadd3(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ IntegerHalfPart partC = (IntegerHalfPart)op.RawOpCode.Extract(31, 2);
+ IntegerHalfPart partB = (IntegerHalfPart)op.RawOpCode.Extract(33, 2);
+ IntegerHalfPart partA = (IntegerHalfPart)op.RawOpCode.Extract(35, 2);
+
+ IntegerShift mode = (IntegerShift)op.RawOpCode.Extract(37, 2);
+
+ bool negateC = op.RawOpCode.Extract(49);
+ bool negateB = op.RawOpCode.Extract(50);
+ bool negateA = op.RawOpCode.Extract(51);
+
+ Operand Extend(Operand src, IntegerHalfPart part)
+ {
+ if (!(op is OpCodeAluReg) || part == IntegerHalfPart.B32)
+ {
+ return src;
+ }
+
+ if (part == IntegerHalfPart.H0)
+ {
+ return context.BitwiseAnd(src, Const(0xffff));
+ }
+ else if (part == IntegerHalfPart.H1)
+ {
+ return context.ShiftRightU32(src, Const(16));
+ }
+ else
+ {
+ //TODO: Warning.
+ }
+
+ return src;
+ }
+
+ Operand srcA = context.INegate(Extend(GetSrcA(context), partA), negateA);
+ Operand srcB = context.INegate(Extend(GetSrcB(context), partB), negateB);
+ Operand srcC = context.INegate(Extend(GetSrcC(context), partC), negateC);
+
+ Operand res = context.IAdd(srcA, srcB);
+
+ if (op is OpCodeAluReg && mode != IntegerShift.NoShift)
+ {
+ if (mode == IntegerShift.ShiftLeft)
+ {
+ res = context.ShiftLeft(res, Const(16));
+ }
+ else if (mode == IntegerShift.ShiftRight)
+ {
+ res = context.ShiftRightU32(res, Const(16));
+ }
+ else
+ {
+ //TODO: Warning.
+ }
+ }
+
+ res = context.IAdd(res, srcC);
+
+ context.Copy(GetDest(context), res);
+
+ //TODO: CC, X, corner cases
+ }
+
+ public static void Imnmx(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isSignedInt = op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand resMin = isSignedInt
+ ? context.IMinimumS32(srcA, srcB)
+ : context.IMinimumU32(srcA, srcB);
+
+ Operand resMax = isSignedInt
+ ? context.IMaximumS32(srcA, srcB)
+ : context.IMaximumU32(srcA, srcB);
+
+ Operand pred = GetPredicate39(context);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax));
+
+ SetZnFlags(context, dest, op.SetCondCode);
+
+ //TODO: X flags.
+ }
+
+ public static void Iscadd(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool negateA = false, negateB = false;
+
+ if (!(op is OpCodeAluImm32))
+ {
+ negateB = op.RawOpCode.Extract(48);
+ negateA = op.RawOpCode.Extract(49);
+ }
+
+ int shift = op is OpCodeAluImm32
+ ? op.RawOpCode.Extract(53, 5)
+ : op.RawOpCode.Extract(39, 5);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ srcA = context.ShiftLeft(srcA, Const(shift));
+
+ srcA = context.INegate(srcA, negateA);
+ srcB = context.INegate(srcB, negateB);
+
+ Operand res = context.IAdd(srcA, srcB);
+
+ context.Copy(GetDest(context), res);
+
+ //TODO: CC, X
+ }
+
+ public static void Iset(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ bool boolFloat = op.RawOpCode.Extract(44);
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned);
+
+ Operand pred = GetPredicate39(context);
+
+ res = GetPredLogicalOp(context, op.LogicalOp, res, pred);
+
+ Operand dest = GetDest(context);
+
+ if (boolFloat)
+ {
+ context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
+ }
+ else
+ {
+ context.Copy(dest, res);
+ }
+
+ //TODO: CC, X
+ }
+
+ public static void Isetp(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand p0Res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned);
+
+ Operand p1Res = context.BitwiseNot(p0Res);
+
+ Operand pred = GetPredicate39(context);
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Lop(EmitterContext context)
+ {
+ IOpCodeLop op = (IOpCodeLop)context.CurrOp;
+
+ Operand srcA = context.BitwiseNot(GetSrcA(context), op.InvertA);
+ Operand srcB = context.BitwiseNot(GetSrcB(context), op.InvertB);
+
+ Operand res = srcB;
+
+ switch (op.LogicalOp)
+ {
+ case LogicalOperation.And: res = context.BitwiseAnd (srcA, srcB); break;
+ case LogicalOperation.Or: res = context.BitwiseOr (srcA, srcB); break;
+ case LogicalOperation.ExclusiveOr: res = context.BitwiseExclusiveOr(srcA, srcB); break;
+ }
+
+ EmitLopPredWrite(context, op, res);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, res);
+
+ SetZnFlags(context, dest, op.SetCondCode, op.Extended);
+ }
+
+ public static void Lop3(EmitterContext context)
+ {
+ IOpCodeLop op = (IOpCodeLop)context.CurrOp;
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+ Operand srcC = GetSrcC(context);
+
+ bool regVariant = op is OpCodeLopReg;
+
+ int truthTable = regVariant
+ ? op.RawOpCode.Extract(28, 8)
+ : op.RawOpCode.Extract(48, 8);
+
+ Operand res = Lop3Expression.GetFromTruthTable(context, srcA, srcB, srcC, truthTable);
+
+ if (regVariant)
+ {
+ EmitLopPredWrite(context, op, res);
+ }
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, res);
+
+ SetZnFlags(context, dest, op.SetCondCode, op.Extended);
+ }
+
+ public static void Psetp(EmitterContext context)
+ {
+ OpCodePsetp op = (OpCodePsetp)context.CurrOp;
+
+ bool invertA = op.RawOpCode.Extract(15);
+ bool invertB = op.RawOpCode.Extract(32);
+
+ Operand srcA = context.BitwiseNot(Register(op.Predicate12), invertA);
+ Operand srcB = context.BitwiseNot(Register(op.Predicate29), invertB);
+
+ Operand p0Res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB);
+
+ Operand p1Res = context.BitwiseNot(p0Res);
+
+ Operand pred = GetPredicate39(context);
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Rro(EmitterContext context)
+ {
+ //This is the range reduction operator,
+ //we translate it as a simple move, as it
+ //should be always followed by a matching
+ //MUFU instruction.
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcB = GetSrcB(context);
+
+ srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
+
+ context.Copy(GetDest(context), srcB);
+ }
+
+ public static void Shl(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isMasked = op.RawOpCode.Extract(39);
+
+ Operand srcB = GetSrcB(context);
+
+ if (isMasked)
+ {
+ srcB = context.BitwiseAnd(srcB, Const(0x1f));
+ }
+
+ Operand res = context.ShiftLeft(GetSrcA(context), srcB);
+
+ if (!isMasked)
+ {
+ //Clamped shift value.
+ Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32));
+
+ res = context.ConditionalSelect(isLessThan32, res, Const(0));
+ }
+
+ //TODO: X, CC
+
+ context.Copy(GetDest(context), res);
+ }
+
+ public static void Shr(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isMasked = op.RawOpCode.Extract(39);
+ bool isReverse = op.RawOpCode.Extract(40);
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ if (isReverse)
+ {
+ srcA = context.BitfieldReverse(srcA);
+ }
+
+ if (isMasked)
+ {
+ srcB = context.BitwiseAnd(srcB, Const(0x1f));
+ }
+
+ Operand res = isSigned
+ ? context.ShiftRightS32(srcA, srcB)
+ : context.ShiftRightU32(srcA, srcB);
+
+ if (!isMasked)
+ {
+ //Clamped shift value.
+ Operand resShiftBy32;
+
+ if (isSigned)
+ {
+ resShiftBy32 = context.ShiftRightS32(srcA, Const(31));
+ }
+ else
+ {
+ resShiftBy32 = Const(0);
+ }
+
+ Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32));
+
+ res = context.ConditionalSelect(isLessThan32, res, resShiftBy32);
+ }
+
+ //TODO: X, CC
+
+ context.Copy(GetDest(context), res);
+ }
+
+ public static void Xmad(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool signedA = context.CurrOp.RawOpCode.Extract(48);
+ bool signedB = context.CurrOp.RawOpCode.Extract(49);
+ bool highA = context.CurrOp.RawOpCode.Extract(53);
+ bool highB = false;
+
+ XmadCMode mode;
+
+ if (op is OpCodeAluReg)
+ {
+ highB = context.CurrOp.RawOpCode.Extract(35);
+
+ mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 3);
+ }
+ else
+ {
+ mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 2);
+
+ if (!(op is OpCodeAluImm))
+ {
+ highB = context.CurrOp.RawOpCode.Extract(52);
+ }
+ }
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+ Operand srcC = GetSrcC(context);
+
+ //XMAD immediates are 16-bits unsigned integers.
+ if (srcB.Type == OperandType.Constant)
+ {
+ srcB = Const(srcB.Value & 0xffff);
+ }
+
+ Operand Extend16To32(Operand src, bool high, bool signed)
+ {
+ if (signed && high)
+ {
+ return context.ShiftRightS32(src, Const(16));
+ }
+ else if (signed)
+ {
+ return context.BitfieldExtractS32(src, Const(0), Const(16));
+ }
+ else if (high)
+ {
+ return context.ShiftRightU32(src, Const(16));
+ }
+ else
+ {
+ return context.BitwiseAnd(src, Const(0xffff));
+ }
+ }
+
+ srcA = Extend16To32(srcA, highA, signedA);
+ srcB = Extend16To32(srcB, highB, signedB);
+
+ bool productShiftLeft = false;
+ bool merge = false;
+
+ if (!(op is OpCodeAluRegCbuf))
+ {
+ productShiftLeft = context.CurrOp.RawOpCode.Extract(36);
+ merge = context.CurrOp.RawOpCode.Extract(37);
+ }
+
+ bool extended;
+
+ if ((op is OpCodeAluReg) || (op is OpCodeAluImm))
+ {
+ extended = context.CurrOp.RawOpCode.Extract(38);
+ }
+ else
+ {
+ extended = context.CurrOp.RawOpCode.Extract(54);
+ }
+
+ Operand res = context.IMultiply(srcA, srcB);
+
+ if (productShiftLeft)
+ {
+ res = context.ShiftLeft(res, Const(16));
+ }
+
+ switch (mode)
+ {
+ case XmadCMode.Cfull: break;
+
+ case XmadCMode.Clo: srcC = Extend16To32(srcC, high: false, signed: false); break;
+ case XmadCMode.Chi: srcC = Extend16To32(srcC, high: true, signed: false); break;
+
+ case XmadCMode.Cbcc:
+ {
+ srcC = context.IAdd(srcC, context.ShiftLeft(GetSrcB(context), Const(16)));
+
+ break;
+ }
+
+ case XmadCMode.Csfu:
+ {
+ Operand signAdjustA = context.ShiftLeft(context.ShiftRightU32(srcA, Const(31)), Const(16));
+ Operand signAdjustB = context.ShiftLeft(context.ShiftRightU32(srcB, Const(31)), Const(16));
+
+ srcC = context.ISubtract(srcC, context.IAdd(signAdjustA, signAdjustB));
+
+ break;
+ }
+
+ default: /* TODO: Warning */ break;
+ }
+
+ Operand product = res;
+
+ if (extended)
+ {
+ //Add with carry.
+ res = context.IAdd(res, context.BitwiseAnd(GetCF(context), Const(1)));
+ }
+ else
+ {
+ //Add (no carry in).
+ res = context.IAdd(res, srcC);
+ }
+
+ SetIaddFlags(context, res, product, srcC, op.SetCondCode, extended);
+
+ if (merge)
+ {
+ res = context.BitwiseAnd(res, Const(0xffff));
+ res = context.BitwiseOr(res, context.ShiftLeft(GetSrcB(context), Const(16)));
+ }
+
+ context.Copy(GetDest(context), res);
+ }
+
+ private static Operand GetIntComparison(
+ EmitterContext context,
+ IntegerCondition cond,
+ Operand srcA,
+ Operand srcB,
+ bool isSigned)
+ {
+ Operand res;
+
+ if (cond == IntegerCondition.Always)
+ {
+ res = Const(IrConsts.True);
+ }
+ else if (cond == IntegerCondition.Never)
+ {
+ res = Const(IrConsts.False);
+ }
+ else
+ {
+ Instruction inst;
+
+ switch (cond)
+ {
+ case IntegerCondition.Less: inst = Instruction.CompareLessU32; break;
+ case IntegerCondition.Equal: inst = Instruction.CompareEqual; break;
+ case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqualU32; break;
+ case IntegerCondition.Greater: inst = Instruction.CompareGreaterU32; break;
+ case IntegerCondition.NotEqual: inst = Instruction.CompareNotEqual; break;
+ case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqualU32; break;
+
+ default: throw new InvalidOperationException($"Unexpected condition \"{cond}\".");
+ }
+
+ if (isSigned)
+ {
+ switch (cond)
+ {
+ case IntegerCondition.Less: inst = Instruction.CompareLess; break;
+ case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break;
+ case IntegerCondition.Greater: inst = Instruction.CompareGreater; break;
+ case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break;
+ }
+ }
+
+ res = context.Add(inst, Local(), srcA, srcB);
+ }
+
+ return res;
+ }
+
+ private static void EmitLopPredWrite(EmitterContext context, IOpCodeLop op, Operand result)
+ {
+ if (op is OpCodeLop opLop && !opLop.Predicate48.IsPT)
+ {
+ Operand pRes;
+
+ if (opLop.CondOp == ConditionalOperation.False)
+ {
+ pRes = Const(IrConsts.False);
+ }
+ else if (opLop.CondOp == ConditionalOperation.True)
+ {
+ pRes = Const(IrConsts.True);
+ }
+ else if (opLop.CondOp == ConditionalOperation.Zero)
+ {
+ pRes = context.ICompareEqual(result, Const(0));
+ }
+ else /* if (opLop.CondOp == ConditionalOperation.NotZero) */
+ {
+ pRes = context.ICompareNotEqual(result, Const(0));
+ }
+
+ context.Copy(Register(opLop.Predicate48), pRes);
+ }
+ }
+
+ private static void SetIaddFlags(
+ EmitterContext context,
+ Operand res,
+ Operand srcA,
+ Operand srcB,
+ bool setCC,
+ bool extended,
+ bool isSubtraction = false)
+ {
+ if (!setCC)
+ {
+ return;
+ }
+
+ if (!extended || isSubtraction)
+ {
+ //C = d < a
+ context.Copy(GetCF(context), context.ICompareLessUnsigned(res, srcA));
+ }
+ else
+ {
+ //C = (d == a && CIn) || d < a
+ Operand tempC0 = context.ICompareEqual (res, srcA);
+ Operand tempC1 = context.ICompareLessUnsigned(res, srcA);
+
+ tempC0 = context.BitwiseAnd(tempC0, GetCF(context));
+
+ context.Copy(GetCF(context), context.BitwiseOr(tempC0, tempC1));
+ }
+
+ //V = (d ^ a) & ~(a ^ b) < 0
+ Operand tempV0 = context.BitwiseExclusiveOr(res, srcA);
+ Operand tempV1 = context.BitwiseExclusiveOr(srcA, srcB);
+
+ tempV1 = context.BitwiseNot(tempV1);
+
+ Operand tempV = context.BitwiseAnd(tempV0, tempV1);
+
+ context.Copy(GetVF(context), context.ICompareLess(tempV, Const(0)));
+
+ SetZnFlags(context, res, setCC: true, extended: extended);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs
new file mode 100644
index 00000000..b5bde1a1
--- /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 00000000..f5e9af03
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs
@@ -0,0 +1,213 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void F2F(EmitterContext context)
+ {
+ OpCodeFArith op = (OpCodeFArith)context.CurrOp;
+
+ FPType srcType = (FPType)op.RawOpCode.Extract(8, 2);
+ FPType dstType = (FPType)op.RawOpCode.Extract(10, 2);
+
+ bool pass = op.RawOpCode.Extract(40);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ pass &= op.RoundingMode == RoundingMode.TowardsNegativeInfinity;
+
+ Operand srcB = context.FPAbsNeg(GetSrcB(context, srcType), absoluteB, negateB);
+
+ if (!pass)
+ {
+ switch (op.RoundingMode)
+ {
+ case RoundingMode.TowardsNegativeInfinity:
+ srcB = context.FPFloor(srcB);
+ break;
+
+ case RoundingMode.TowardsPositiveInfinity:
+ srcB = context.FPCeiling(srcB);
+ break;
+
+ case RoundingMode.TowardsZero:
+ srcB = context.FPTruncate(srcB);
+ break;
+ }
+ }
+
+ srcB = context.FPSaturate(srcB, op.Saturate);
+
+ WriteFP(context, dstType, srcB);
+
+ //TODO: CC.
+ }
+
+ public static void F2I(EmitterContext context)
+ {
+ OpCodeFArith op = (OpCodeFArith)context.CurrOp;
+
+ IntegerType intType = (IntegerType)op.RawOpCode.Extract(8, 2);
+
+ bool isSmallInt = intType <= IntegerType.U16;
+
+ FPType floatType = (FPType)op.RawOpCode.Extract(10, 2);
+
+ bool isSignedInt = op.RawOpCode.Extract(12);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ if (isSignedInt)
+ {
+ intType |= IntegerType.S8;
+ }
+
+ Operand srcB = context.FPAbsNeg(GetSrcB(context, floatType), absoluteB, negateB);
+
+ switch (op.RoundingMode)
+ {
+ case RoundingMode.TowardsNegativeInfinity:
+ srcB = context.FPFloor(srcB);
+ break;
+
+ case RoundingMode.TowardsPositiveInfinity:
+ srcB = context.FPCeiling(srcB);
+ break;
+
+ case RoundingMode.TowardsZero:
+ srcB = context.FPTruncate(srcB);
+ break;
+ }
+
+ srcB = context.FPConvertToS32(srcB);
+
+ //TODO: S/U64, conversion overflow handling.
+ if (intType != IntegerType.S32)
+ {
+ int min = GetIntMin(intType);
+ int max = GetIntMax(intType);
+
+ srcB = isSignedInt
+ ? context.IClampS32(srcB, Const(min), Const(max))
+ : context.IClampU32(srcB, Const(min), Const(max));
+ }
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, srcB);
+
+ //TODO: CC.
+ }
+
+ public static void I2F(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ FPType dstType = (FPType)op.RawOpCode.Extract(8, 2);
+
+ IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2);
+
+ bool isSmallInt = srcType <= IntegerType.U16;
+
+ bool isSignedInt = op.RawOpCode.Extract(13);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcB = context.IAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ if (isSmallInt)
+ {
+ int size = srcType == IntegerType.U16 ? 16 : 8;
+
+ srcB = isSignedInt
+ ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size))
+ : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size));
+ }
+
+ srcB = isSignedInt
+ ? context.IConvertS32ToFP(srcB)
+ : context.IConvertU32ToFP(srcB);
+
+ WriteFP(context, dstType, srcB);
+
+ //TODO: CC.
+ }
+
+ public static void I2I(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ IntegerType dstType = (IntegerType)op.RawOpCode.Extract(8, 2);
+ IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2);
+
+ if (srcType == IntegerType.U64 || dstType == IntegerType.U64)
+ {
+ //TODO: Warning. This instruction doesn't support 64-bits integers
+ }
+
+ bool srcIsSmallInt = srcType <= IntegerType.U16;
+
+ bool dstIsSignedInt = op.RawOpCode.Extract(12);
+ bool srcIsSignedInt = op.RawOpCode.Extract(13);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcB = GetSrcB(context);
+
+ if (srcIsSmallInt)
+ {
+ int size = srcType == IntegerType.U16 ? 16 : 8;
+
+ srcB = srcIsSignedInt
+ ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size))
+ : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size));
+ }
+
+ srcB = context.IAbsNeg(srcB, absoluteB, negateB);
+
+ if (op.Saturate)
+ {
+ if (dstIsSignedInt)
+ {
+ dstType |= IntegerType.S8;
+ }
+
+ int min = GetIntMin(dstType);
+ int max = GetIntMax(dstType);
+
+ srcB = dstIsSignedInt
+ ? context.IClampS32(srcB, Const(min), Const(max))
+ : context.IClampU32(srcB, Const(min), Const(max));
+ }
+
+ context.Copy(GetDest(context), srcB);
+
+ //TODO: CC.
+ }
+
+ private static void WriteFP(EmitterContext context, FPType type, Operand srcB)
+ {
+ Operand dest = GetDest(context);
+
+ if (type == FPType.FP32)
+ {
+ context.Copy(dest, srcB);
+ }
+ else if (type == FPType.FP16)
+ {
+ context.Copy(dest, context.PackHalf2x16(srcB, ConstF(0)));
+ }
+ else
+ {
+ //TODO.
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs
new file mode 100644
index 00000000..72492470
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs
@@ -0,0 +1,369 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Fadd(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool absoluteA = op.AbsoluteA, absoluteB, negateA, negateB;
+
+ if (op is OpCodeFArithImm32)
+ {
+ negateB = op.RawOpCode.Extract(53);
+ negateA = op.RawOpCode.Extract(56);
+ absoluteB = op.RawOpCode.Extract(57);
+ }
+ else
+ {
+ negateB = op.RawOpCode.Extract(45);
+ negateA = op.RawOpCode.Extract(48);
+ absoluteB = op.RawOpCode.Extract(49);
+ }
+
+ Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.FPSaturate(context.FPAdd(srcA, srcB), op.Saturate));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Ffma(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool negateB = op.RawOpCode.Extract(48);
+ bool negateC = op.RawOpCode.Extract(49);
+
+ Operand srcA = GetSrcA(context);
+
+ Operand srcB = context.FPNegate(GetSrcB(context), negateB);
+ Operand srcC = context.FPNegate(GetSrcC(context), negateC);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC), op.Saturate));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Fmnmx(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool absoluteA = op.AbsoluteA;
+ bool negateB = op.RawOpCode.Extract(45);
+ bool negateA = op.RawOpCode.Extract(48);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ Operand resMin = context.FPMinimum(srcA, srcB);
+ Operand resMax = context.FPMaximum(srcA, srcB);
+
+ Operand pred = GetPredicate39(context);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Fmul(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool negateB = !(op is OpCodeFArithImm32) && op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+
+ Operand srcB = context.FPNegate(GetSrcB(context), negateB);
+
+ switch (op.Scale)
+ {
+ case FmulScale.None: break;
+
+ case FmulScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break;
+ case FmulScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break;
+ case FmulScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break;
+ case FmulScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break;
+ case FmulScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break;
+ case FmulScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break;
+
+ default: break; //TODO: Warning.
+ }
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.FPSaturate(context.FPMultiply(srcA, srcB), op.Saturate));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Fset(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4);
+
+ bool negateA = op.RawOpCode.Extract(43);
+ bool absoluteB = op.RawOpCode.Extract(44);
+ bool boolFloat = op.RawOpCode.Extract(52);
+ bool negateB = op.RawOpCode.Extract(53);
+ bool absoluteA = op.RawOpCode.Extract(54);
+
+ Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ Operand res = GetFPComparison(context, cmpOp, srcA, srcB);
+
+ Operand pred = GetPredicate39(context);
+
+ res = GetPredLogicalOp(context, op.LogicalOp, res, pred);
+
+ Operand dest = GetDest(context);
+
+ if (boolFloat)
+ {
+ context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
+ }
+ else
+ {
+ context.Copy(dest, res);
+ }
+
+ //TODO: CC, X
+ }
+
+ public static void Fsetp(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4);
+
+ bool absoluteA = op.RawOpCode.Extract(7);
+ bool negateA = op.RawOpCode.Extract(43);
+ bool absoluteB = op.RawOpCode.Extract(44);
+
+ Operand srcA = context.FPAbsNeg (GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsolute(GetSrcB(context), absoluteB);
+
+ Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB);
+
+ Operand p1Res = context.BitwiseNot(p0Res);
+
+ Operand pred = GetPredicate39(context);
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Hadd2(EmitterContext context)
+ {
+ Hadd2Hmul2Impl(context, isAdd: true);
+ }
+
+ public static void Hfma2(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ Operand[] srcA = GetHfmaSrcA(context);
+ Operand[] srcB = GetHfmaSrcB(context);
+ Operand[] srcC = GetHfmaSrcC(context);
+
+ Operand[] res = new Operand[2];
+
+ for (int index = 0; index < res.Length; index++)
+ {
+ res[index] = context.FPFusedMultiplyAdd(srcA[index], srcB[index], srcC[index]);
+
+ res[index] = context.FPSaturate(res[index], op.Saturate);
+ }
+
+ context.Copy(GetDest(context), GetHalfPacked(context, res));
+ }
+
+ public static void Hmul2(EmitterContext context)
+ {
+ Hadd2Hmul2Impl(context, isAdd: false);
+ }
+
+ private static void Hadd2Hmul2Impl(EmitterContext context, bool isAdd)
+ {
+ OpCode op = context.CurrOp;
+
+ bool saturate = op.RawOpCode.Extract(op is OpCodeAluImm32 ? 52 : 32);
+
+ Operand[] srcA = GetHalfSrcA(context);
+ Operand[] srcB = GetHalfSrcB(context);
+
+ Operand[] res = new Operand[2];
+
+ for (int index = 0; index < res.Length; index++)
+ {
+ if (isAdd)
+ {
+ res[index] = context.FPAdd(srcA[index], srcB[index]);
+ }
+ else
+ {
+ res[index] = context.FPMultiply(srcA[index], srcB[index]);
+ }
+
+ res[index] = context.FPSaturate(res[index], saturate);
+ }
+
+ context.Copy(GetDest(context), GetHalfPacked(context, res));
+ }
+
+ public static void Mufu(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool negateB = op.RawOpCode.Extract(48);
+
+ Operand res = context.FPAbsNeg(GetSrcA(context), op.AbsoluteA, negateB);
+
+ MufuOperation subOp = (MufuOperation)context.CurrOp.RawOpCode.Extract(20, 4);
+
+ switch (subOp)
+ {
+ case MufuOperation.Cosine:
+ res = context.FPCosine(res);
+ break;
+
+ case MufuOperation.Sine:
+ res = context.FPSine(res);
+ break;
+
+ case MufuOperation.ExponentB2:
+ res = context.FPExponentB2(res);
+ break;
+
+ case MufuOperation.LogarithmB2:
+ res = context.FPLogarithmB2(res);
+ break;
+
+ case MufuOperation.Reciprocal:
+ res = context.FPReciprocal(res);
+ break;
+
+ case MufuOperation.ReciprocalSquareRoot:
+ res = context.FPReciprocalSquareRoot(res);
+ break;
+
+ case MufuOperation.SquareRoot:
+ res = context.FPSquareRoot(res);
+ break;
+
+ default: /* TODO */ break;
+ }
+
+ context.Copy(GetDest(context), context.FPSaturate(res, op.Saturate));
+ }
+
+ private static Operand GetFPComparison(
+ EmitterContext context,
+ Condition cond,
+ Operand srcA,
+ Operand srcB)
+ {
+ Operand res;
+
+ if (cond == Condition.Always)
+ {
+ res = Const(IrConsts.True);
+ }
+ else if (cond == Condition.Never)
+ {
+ res = Const(IrConsts.False);
+ }
+ else if (cond == Condition.Nan || cond == Condition.Number)
+ {
+ res = context.BitwiseOr(context.IsNan(srcA), context.IsNan(srcB));
+
+ if (cond == Condition.Number)
+ {
+ res = context.BitwiseNot(res);
+ }
+ }
+ else
+ {
+ Instruction inst;
+
+ switch (cond & ~Condition.Nan)
+ {
+ case Condition.Less: inst = Instruction.CompareLess; break;
+ case Condition.Equal: inst = Instruction.CompareEqual; break;
+ case Condition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break;
+ case Condition.Greater: inst = Instruction.CompareGreater; break;
+ case Condition.NotEqual: inst = Instruction.CompareNotEqual; break;
+ case Condition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break;
+
+ default: throw new InvalidOperationException($"Unexpected condition \"{cond}\".");
+ }
+
+ res = context.Add(inst | Instruction.FP, Local(), srcA, srcB);
+
+ if ((cond & Condition.Nan) != 0)
+ {
+ res = context.BitwiseOr(res, context.IsNan(srcA));
+ res = context.BitwiseOr(res, context.IsNan(srcB));
+ }
+ }
+
+ return res;
+ }
+
+ private static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC)
+ {
+ if (setCC)
+ {
+ context.Copy(GetZF(context), context.FPCompareEqual(dest, ConstF(0)));
+ context.Copy(GetNF(context), context.FPCompareLess (dest, ConstF(0)));
+ }
+ }
+
+ private static Operand[] GetHfmaSrcA(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ return GetHalfSources(context, GetSrcA(context), op.SwizzleA);
+ }
+
+ private static Operand[] GetHfmaSrcB(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ Operand[] operands = GetHalfSources(context, GetSrcB(context), op.SwizzleB);
+
+ return FPAbsNeg(context, operands, false, op.NegateB);
+ }
+
+ private static Operand[] GetHfmaSrcC(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ Operand[] operands = GetHalfSources(context, GetSrcC(context), op.SwizzleC);
+
+ return FPAbsNeg(context, operands, false, op.NegateC);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs
new file mode 100644
index 00000000..c4523f75
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs
@@ -0,0 +1,107 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System.Collections.Generic;
+using System.Linq;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Bra(EmitterContext context)
+ {
+ EmitBranch(context, context.CurrBlock.Branch.Address);
+ }
+
+ public static void Exit(EmitterContext context)
+ {
+ OpCodeExit op = (OpCodeExit)context.CurrOp;
+
+ //TODO: Figure out how this is supposed to work in the
+ //presence of other condition codes.
+ if (op.Condition == Condition.Always)
+ {
+ context.Return();
+ }
+ }
+
+ public static void Kil(EmitterContext context)
+ {
+ context.Discard();
+ }
+
+ public static void Ssy(EmitterContext context)
+ {
+ OpCodeSsy op = (OpCodeSsy)context.CurrOp;
+
+ foreach (KeyValuePair<OpCodeSync, Operand> 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<OpCodeSsy, int> 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 00000000..e31528d0
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs
@@ -0,0 +1,267 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static class InstEmitHelper
+ {
+ public static Operand GetZF(EmitterContext context)
+ {
+ return Register(0, RegisterType.Flag);
+ }
+
+ public static Operand GetNF(EmitterContext context)
+ {
+ return Register(1, RegisterType.Flag);
+ }
+
+ public static Operand GetCF(EmitterContext context)
+ {
+ return Register(2, RegisterType.Flag);
+ }
+
+ public static Operand GetVF(EmitterContext context)
+ {
+ return Register(3, RegisterType.Flag);
+ }
+
+ public static Operand GetDest(EmitterContext context)
+ {
+ return Register(((IOpCodeRd)context.CurrOp).Rd);
+ }
+
+ public static Operand GetSrcA(EmitterContext context)
+ {
+ return Register(((IOpCodeRa)context.CurrOp).Ra);
+ }
+
+ public static Operand GetSrcB(EmitterContext context, FPType floatType)
+ {
+ if (floatType == FPType.FP32)
+ {
+ return GetSrcB(context);
+ }
+ else if (floatType == FPType.FP16)
+ {
+ int h = context.CurrOp.RawOpCode.Extract(41, 1);
+
+ return GetHalfSources(context, GetSrcB(context), FPHalfSwizzle.FP16)[h];
+ }
+ else if (floatType == FPType.FP64)
+ {
+ //TODO.
+ }
+
+ throw new ArgumentException($"Invalid floating point type \"{floatType}\".");
+ }
+
+ public static Operand GetSrcB(EmitterContext context)
+ {
+ switch (context.CurrOp)
+ {
+ case IOpCodeCbuf op:
+ return Cbuf(op.Slot, op.Offset);
+
+ case IOpCodeImm op:
+ return Const(op.Immediate);
+
+ case IOpCodeImmF op:
+ return ConstF(op.Immediate);
+
+ case IOpCodeReg op:
+ return Register(op.Rb);
+
+ case IOpCodeRegCbuf op:
+ return Register(op.Rc);
+ }
+
+ throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\".");
+ }
+
+ public static Operand GetSrcC(EmitterContext context)
+ {
+ switch (context.CurrOp)
+ {
+ case IOpCodeRegCbuf op:
+ return Cbuf(op.Slot, op.Offset);
+
+ case IOpCodeRc op:
+ return Register(op.Rc);
+ }
+
+ throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\".");
+ }
+
+ public static Operand[] GetHalfSrcA(EmitterContext context)
+ {
+ OpCode op = context.CurrOp;
+
+ bool absoluteA = false, negateA = false;
+
+ if (op is IOpCodeCbuf || op is IOpCodeImm)
+ {
+ negateA = op.RawOpCode.Extract(43);
+ absoluteA = op.RawOpCode.Extract(44);
+ }
+ else if (op is IOpCodeReg)
+ {
+ absoluteA = op.RawOpCode.Extract(44);
+ }
+ else if (op is OpCodeAluImm32 && op.Emitter == InstEmit.Hadd2)
+ {
+ negateA = op.RawOpCode.Extract(56);
+ }
+
+ FPHalfSwizzle swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(47, 2);
+
+ Operand[] operands = GetHalfSources(context, GetSrcA(context), swizzle);
+
+ return FPAbsNeg(context, operands, absoluteA, negateA);
+ }
+
+ public static Operand[] GetHalfSrcB(EmitterContext context)
+ {
+ OpCode op = context.CurrOp;
+
+ FPHalfSwizzle swizzle = FPHalfSwizzle.FP16;
+
+ bool absoluteB = false, negateB = false;
+
+ if (op is IOpCodeReg)
+ {
+ swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(28, 2);
+
+ absoluteB = op.RawOpCode.Extract(30);
+ negateB = op.RawOpCode.Extract(31);
+ }
+ else if (op is IOpCodeCbuf)
+ {
+ swizzle = FPHalfSwizzle.FP32;
+
+ absoluteB = op.RawOpCode.Extract(54);
+ }
+
+ Operand[] operands = GetHalfSources(context, GetSrcB(context), swizzle);
+
+ return FPAbsNeg(context, operands, absoluteB, negateB);
+ }
+
+ public static Operand[] FPAbsNeg(EmitterContext context, Operand[] operands, bool abs, bool neg)
+ {
+ for (int index = 0; index < operands.Length; index++)
+ {
+ operands[index] = context.FPAbsNeg(operands[index], abs, neg);
+ }
+
+ return operands;
+ }
+
+ public static Operand[] GetHalfSources(EmitterContext context, Operand src, FPHalfSwizzle swizzle)
+ {
+ switch (swizzle)
+ {
+ case FPHalfSwizzle.FP16:
+ return new Operand[]
+ {
+ context.UnpackHalf2x16Low (src),
+ context.UnpackHalf2x16High(src)
+ };
+
+ case FPHalfSwizzle.FP32: return new Operand[] { src, src };
+
+ case FPHalfSwizzle.DupH0:
+ return new Operand[]
+ {
+ context.UnpackHalf2x16Low(src),
+ context.UnpackHalf2x16Low(src)
+ };
+
+ case FPHalfSwizzle.DupH1:
+ return new Operand[]
+ {
+ context.UnpackHalf2x16High(src),
+ context.UnpackHalf2x16High(src)
+ };
+ }
+
+ throw new ArgumentException($"Invalid swizzle \"{swizzle}\".");
+ }
+
+ public static Operand GetHalfPacked(EmitterContext context, Operand[] results)
+ {
+ OpCode op = context.CurrOp;
+
+ FPHalfSwizzle swizzle = FPHalfSwizzle.FP16;
+
+ if (!(op is OpCodeAluImm32))
+ {
+ swizzle = (FPHalfSwizzle)context.CurrOp.RawOpCode.Extract(49, 2);
+ }
+
+ switch (swizzle)
+ {
+ case FPHalfSwizzle.FP16: return context.PackHalf2x16(results[0], results[1]);
+
+ case FPHalfSwizzle.FP32: return results[0];
+
+ case FPHalfSwizzle.DupH0:
+ {
+ Operand h1 = GetHalfDest(context, isHigh: true);
+
+ return context.PackHalf2x16(results[0], h1);
+ }
+
+ case FPHalfSwizzle.DupH1:
+ {
+ Operand h0 = GetHalfDest(context, isHigh: false);
+
+ return context.PackHalf2x16(h0, results[1]);
+ }
+ }
+
+ throw new ArgumentException($"Invalid swizzle \"{swizzle}\".");
+ }
+
+ public static Operand GetHalfDest(EmitterContext context, bool isHigh)
+ {
+ if (isHigh)
+ {
+ return context.UnpackHalf2x16High(GetDest(context));
+ }
+ else
+ {
+ return context.UnpackHalf2x16Low(GetDest(context));
+ }
+ }
+
+ public static Operand GetPredicate39(EmitterContext context)
+ {
+ IOpCodeAlu op = (IOpCodeAlu)context.CurrOp;
+
+ Operand local = Register(op.Predicate39);
+
+ if (op.InvertP)
+ {
+ local = context.BitwiseNot(local);
+ }
+
+ return local;
+ }
+
+ public static Operand SignExtendTo32(EmitterContext context, Operand src, int srcBits)
+ {
+ return context.BitfieldExtractS32(src, Const(0), Const(srcBits));
+ }
+
+ public static Operand ZeroExtendTo32(EmitterContext context, Operand src, int srcBits)
+ {
+ int mask = (int)(0xffffffffu >> (32 - srcBits));
+
+ return context.BitwiseAnd(src, Const(mask));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs
new file mode 100644
index 00000000..d81e97a1
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs
@@ -0,0 +1,138 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Ald(EmitterContext context)
+ {
+ OpCodeAttribute op = (OpCodeAttribute)context.CurrOp;
+
+ Operand[] elems = new Operand[op.Count];
+
+ for (int index = 0; index < op.Count; index++)
+ {
+ Operand src = Attribute(op.AttributeOffset + index * 4);
+
+ context.Copy(elems[index] = Local(), src);
+ }
+
+ for (int index = 0; index < op.Count; index++)
+ {
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ if (rd.IsRZ)
+ {
+ break;
+ }
+
+ context.Copy(Register(rd), elems[index]);
+ }
+ }
+
+ public static void Ast(EmitterContext context)
+ {
+ OpCodeAttribute op = (OpCodeAttribute)context.CurrOp;
+
+ for (int index = 0; index < op.Count; index++)
+ {
+ if (op.Rd.Index + index > RegisterConsts.RegisterZeroIndex)
+ {
+ break;
+ }
+
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ Operand dest = Attribute(op.AttributeOffset + index * 4);
+
+ context.Copy(dest, Register(rd));
+ }
+ }
+
+ public static void Ipa(EmitterContext context)
+ {
+ OpCodeIpa op = (OpCodeIpa)context.CurrOp;
+
+ Operand srcA = new Operand(OperandType.Attribute, op.AttributeOffset);
+
+ Operand srcB = GetSrcB(context);
+
+ context.Copy(GetDest(context), srcA);
+ }
+
+ public static void Ldc(EmitterContext context)
+ {
+ OpCodeLdc op = (OpCodeLdc)context.CurrOp;
+
+ if (op.Size > IntegerSize.B64)
+ {
+ //TODO: Warning.
+ }
+
+ bool isSmallInt = op.Size < IntegerSize.B32;
+
+ int count = op.Size == IntegerSize.B64 ? 2 : 1;
+
+ Operand baseOffset = context.Copy(GetSrcA(context));
+
+ for (int index = 0; index < count; index++)
+ {
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ if (rd.IsRZ)
+ {
+ break;
+ }
+
+ Operand offset = context.IAdd(baseOffset, Const((op.Offset + index) * 4));
+
+ Operand value = context.LoadConstant(Const(op.Slot), offset);
+
+ if (isSmallInt)
+ {
+ Operand shift = context.BitwiseAnd(baseOffset, Const(3));
+
+ value = context.ShiftRightU32(value, shift);
+
+ switch (op.Size)
+ {
+ case IntegerSize.U8: value = ZeroExtendTo32(context, value, 8); break;
+ case IntegerSize.U16: value = ZeroExtendTo32(context, value, 16); break;
+ case IntegerSize.S8: value = SignExtendTo32(context, value, 8); break;
+ case IntegerSize.S16: value = SignExtendTo32(context, value, 16); break;
+ }
+ }
+
+ context.Copy(Register(rd), value);
+ }
+ }
+
+ public static void Out(EmitterContext context)
+ {
+ OpCode op = context.CurrOp;
+
+ bool emit = op.RawOpCode.Extract(39);
+ bool cut = op.RawOpCode.Extract(40);
+
+ if (!(emit || cut))
+ {
+ //TODO: Warning.
+ }
+
+ if (emit)
+ {
+ context.EmitVertex();
+ }
+
+ if (cut)
+ {
+ context.EndPrimitive();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs
new file mode 100644
index 00000000..b74dbfd7
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Mov(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ context.Copy(GetDest(context), GetSrcB(context));
+ }
+
+ public static void Sel(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ Operand pred = GetPredicate39(context);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand res = context.ConditionalSelect(pred, srcA, srcB);
+
+ context.Copy(GetDest(context), res);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs
new file mode 100644
index 00000000..1b19d901
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs
@@ -0,0 +1,776 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Tex(EmitterContext context)
+ {
+ Tex(context, TextureFlags.None);
+ }
+
+ public static void Tex_B(EmitterContext context)
+ {
+ Tex(context, TextureFlags.Bindless);
+ }
+
+ public static void Tld(EmitterContext context)
+ {
+ Tex(context, TextureFlags.IntCoords);
+ }
+
+ public static void Tld_B(EmitterContext context)
+ {
+ Tex(context, TextureFlags.IntCoords | TextureFlags.Bindless);
+ }
+
+ public static void Texs(EmitterContext context)
+ {
+ OpCodeTextureScalar op = (OpCodeTextureScalar)context.CurrOp;
+
+ if (op.Rd0.IsRZ && op.Rd1.IsRZ)
+ {
+ return;
+ }
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ int raIndex = op.Ra.Index;
+ int rbIndex = op.Rb.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ Operand Rb()
+ {
+ if (rbIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(rbIndex++, RegisterType.Gpr));
+ }
+
+ void AddTextureOffset(int coordsCount, int stride, int size)
+ {
+ Operand packedOffs = Rb();
+
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * stride), Const(size)));
+ }
+ }
+
+ TextureType type;
+ TextureFlags flags;
+
+ if (op is OpCodeTexs texsOp)
+ {
+ type = GetTextureType (texsOp.Type);
+ flags = GetTextureFlags(texsOp.Type);
+
+ if ((type & TextureType.Array) != 0)
+ {
+ Operand arrayIndex = Ra();
+
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+
+ sourcesList.Add(arrayIndex);
+
+ if ((type & TextureType.Shadow) != 0)
+ {
+ sourcesList.Add(Rb());
+ }
+
+ if ((flags & TextureFlags.LodLevel) != 0)
+ {
+ sourcesList.Add(ConstF(0));
+ }
+ }
+ else
+ {
+ switch (texsOp.Type)
+ {
+ case TextureScalarType.Texture1DLodZero:
+ sourcesList.Add(Ra());
+ break;
+
+ case TextureScalarType.Texture2D:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case TextureScalarType.Texture2DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(ConstF(0));
+ break;
+
+ case TextureScalarType.Texture2DLodLevel:
+ case TextureScalarType.Texture2DDepthCompare:
+ case TextureScalarType.Texture3D:
+ case TextureScalarType.TextureCube:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case TextureScalarType.Texture2DLodZeroDepthCompare:
+ case TextureScalarType.Texture3DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(ConstF(0));
+ break;
+
+ case TextureScalarType.Texture2DLodLevelDepthCompare:
+ case TextureScalarType.TextureCubeLodLevel:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Rb());
+ break;
+ }
+ }
+ }
+ else if (op is OpCodeTlds tldsOp)
+ {
+ type = GetTextureType (tldsOp.Type);
+ flags = GetTextureFlags(tldsOp.Type) | TextureFlags.IntCoords;
+
+ switch (tldsOp.Type)
+ {
+ case TexelLoadScalarType.Texture1DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadScalarType.Texture1DLodLevel:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case TexelLoadScalarType.Texture2DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadScalarType.Texture2DLodZeroOffset:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadScalarType.Texture2DLodZeroMultisample:
+ case TexelLoadScalarType.Texture2DLodLevel:
+ case TexelLoadScalarType.Texture2DLodLevelOffset:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case TexelLoadScalarType.Texture3DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadScalarType.Texture2DArrayLodZero:
+ sourcesList.Add(Rb());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Const(0));
+ break;
+ }
+
+ if ((flags & TextureFlags.Offset) != 0)
+ {
+ AddTextureOffset(type.GetCoordsCount(), 4, 4);
+ }
+ }
+ else if (op is OpCodeTld4s tld4sOp)
+ {
+ if (!(tld4sOp.HasDepthCompare || tld4sOp.HasOffset))
+ {
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ }
+ else
+ {
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ }
+
+ type = TextureType.Texture2D;
+ flags = TextureFlags.Gather;
+
+ if (tld4sOp.HasDepthCompare)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureType.Shadow;
+ }
+
+ if (tld4sOp.HasOffset)
+ {
+ AddTextureOffset(type.GetCoordsCount(), 8, 6);
+
+ flags |= TextureFlags.Offset;
+ }
+
+ sourcesList.Add(Const(tld4sOp.GatherCompIndex));
+ }
+ else
+ {
+ throw new InvalidOperationException($"Invalid opcode type \"{op.GetType().Name}\".");
+ }
+
+ Operand[] sources = sourcesList.ToArray();
+
+ Operand[] rd0 = new Operand[2] { ConstF(0), ConstF(0) };
+ Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) };
+
+ int destIncrement = 0;
+
+ Operand GetDest()
+ {
+ int high = destIncrement >> 1;
+ int low = destIncrement & 1;
+
+ destIncrement++;
+
+ if (op.IsFp16)
+ {
+ return high != 0
+ ? (rd1[low] = Local())
+ : (rd0[low] = Local());
+ }
+ else
+ {
+ int rdIndex = high != 0 ? op.Rd1.Index : op.Rd0.Index;
+
+ if (rdIndex < RegisterConsts.RegisterZeroIndex)
+ {
+ rdIndex += low;
+ }
+
+ return Register(rdIndex, RegisterType.Gpr);
+ }
+ }
+
+ int handle = op.Immediate;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+
+ if (op.IsFp16)
+ {
+ context.Copy(Register(op.Rd0), context.PackHalf2x16(rd0[0], rd0[1]));
+ context.Copy(Register(op.Rd1), context.PackHalf2x16(rd1[0], rd1[1]));
+ }
+ }
+
+ public static void Tld4(EmitterContext context)
+ {
+ OpCodeTld4 op = (OpCodeTld4)context.CurrOp;
+
+ if (op.Rd.IsRZ)
+ {
+ return;
+ }
+
+ int raIndex = op.Ra.Index;
+ int rbIndex = op.Rb.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ Operand Rb()
+ {
+ if (rbIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(rbIndex++, RegisterType.Gpr));
+ }
+
+ Operand arrayIndex = op.IsArray ? Ra() : null;
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ TextureType type = GetTextureType(op.Dimensions);
+
+ TextureFlags flags = TextureFlags.Gather;
+
+ int coordsCount = type.GetCoordsCount();
+
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(Ra());
+ }
+
+ if (op.IsArray)
+ {
+ sourcesList.Add(arrayIndex);
+
+ type |= TextureType.Array;
+ }
+
+ Operand[] packedOffs = new Operand[2];
+
+ packedOffs[0] = op.Offset != TextureGatherOffset.None ? Rb() : null;
+ packedOffs[1] = op.Offset == TextureGatherOffset.Offsets ? Rb() : null;
+
+ if (op.HasDepthCompare)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureType.Shadow;
+ }
+
+ if (op.Offset != TextureGatherOffset.None)
+ {
+ int offsetTexelsCount = op.Offset == TextureGatherOffset.Offsets ? 4 : 1;
+
+ for (int index = 0; index < coordsCount * offsetTexelsCount; index++)
+ {
+ Operand packed = packedOffs[(index >> 2) & 1];
+
+ sourcesList.Add(context.BitfieldExtractS32(packed, Const((index & 3) * 8), Const(6)));
+ }
+
+ flags |= op.Offset == TextureGatherOffset.Offsets
+ ? TextureFlags.Offsets
+ : TextureFlags.Offset;
+ }
+
+ sourcesList.Add(Const(op.GatherCompIndex));
+
+ Operand[] sources = sourcesList.ToArray();
+
+ int rdIndex = op.Rd.Index;
+
+ Operand GetDest()
+ {
+ if (rdIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return Register(rdIndex++, RegisterType.Gpr);
+ }
+
+ int handle = op.Immediate;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+ }
+
+ public static void Txq(EmitterContext context)
+ {
+ Txq(context, bindless: false);
+ }
+
+ public static void Txq_B(EmitterContext context)
+ {
+ Txq(context, bindless: true);
+ }
+
+ private static void Txq(EmitterContext context, bool bindless)
+ {
+ OpCodeTex op = (OpCodeTex)context.CurrOp;
+
+ if (op.Rd.IsRZ)
+ {
+ return;
+ }
+
+ TextureProperty property = (TextureProperty)op.RawOpCode.Extract(22, 6);
+
+ //TODO: Validate and use property.
+ Instruction inst = Instruction.TextureSize;
+
+ TextureType type = TextureType.Texture2D;
+
+ TextureFlags flags = bindless ? TextureFlags.Bindless : TextureFlags.None;
+
+ int raIndex = op.Ra.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ if (bindless)
+ {
+ sourcesList.Add(Ra());
+ }
+
+ sourcesList.Add(Ra());
+
+ Operand[] sources = sourcesList.ToArray();
+
+ int rdIndex = op.Rd.Index;
+
+ Operand GetDest()
+ {
+ if (rdIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return Register(rdIndex++, RegisterType.Gpr);
+ }
+
+ int handle = !bindless ? op.Immediate : 0;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ inst,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+ }
+
+ private static void Tex(EmitterContext context, TextureFlags flags)
+ {
+ OpCodeTexture op = (OpCodeTexture)context.CurrOp;
+
+ bool isBindless = (flags & TextureFlags.Bindless) != 0;
+ bool intCoords = (flags & TextureFlags.IntCoords) != 0;
+
+ if (op.Rd.IsRZ)
+ {
+ return;
+ }
+
+ int raIndex = op.Ra.Index;
+ int rbIndex = op.Rb.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ Operand Rb()
+ {
+ if (rbIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(rbIndex++, RegisterType.Gpr));
+ }
+
+ Operand arrayIndex = op.IsArray ? Ra() : null;
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ if (isBindless)
+ {
+ sourcesList.Add(Rb());
+ }
+
+ TextureType type = GetTextureType(op.Dimensions);
+
+ int coordsCount = type.GetCoordsCount();
+
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(Ra());
+ }
+
+ if (op.IsArray)
+ {
+ sourcesList.Add(arrayIndex);
+
+ type |= TextureType.Array;
+ }
+
+ bool hasLod = op.LodMode > TextureLodMode.LodZero;
+
+ Operand lodValue = hasLod ? Rb() : ConstF(0);
+
+ Operand packedOffs = op.HasOffset ? Rb() : null;
+
+ if (op.HasDepthCompare)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureType.Shadow;
+ }
+
+ if ((op.LodMode == TextureLodMode.LodZero ||
+ op.LodMode == TextureLodMode.LodLevel ||
+ op.LodMode == TextureLodMode.LodLevelA) && !op.IsMultisample)
+ {
+ sourcesList.Add(lodValue);
+
+ flags |= TextureFlags.LodLevel;
+ }
+
+ if (op.HasOffset)
+ {
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * 4), Const(4)));
+ }
+
+ flags |= TextureFlags.Offset;
+ }
+
+ if (op.LodMode == TextureLodMode.LodBias ||
+ op.LodMode == TextureLodMode.LodBiasA)
+ {
+ sourcesList.Add(lodValue);
+
+ flags |= TextureFlags.LodBias;
+ }
+
+ if (op.IsMultisample)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureType.Multisample;
+ }
+
+ Operand[] sources = sourcesList.ToArray();
+
+ int rdIndex = op.Rd.Index;
+
+ Operand GetDest()
+ {
+ if (rdIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return Register(rdIndex++, RegisterType.Gpr);
+ }
+
+ int handle = !isBindless ? op.Immediate : 0;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+ }
+
+ private static TextureType GetTextureType(TextureDimensions dimensions)
+ {
+ switch (dimensions)
+ {
+ case TextureDimensions.Texture1D: return TextureType.Texture1D;
+ case TextureDimensions.Texture2D: return TextureType.Texture2D;
+ case TextureDimensions.Texture3D: return TextureType.Texture3D;
+ case TextureDimensions.TextureCube: return TextureType.TextureCube;
+ }
+
+ throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\".");
+ }
+
+ private static TextureType GetTextureType(TextureScalarType type)
+ {
+ switch (type)
+ {
+ case TextureScalarType.Texture1DLodZero:
+ return TextureType.Texture1D;
+
+ case TextureScalarType.Texture2D:
+ case TextureScalarType.Texture2DLodZero:
+ case TextureScalarType.Texture2DLodLevel:
+ return TextureType.Texture2D;
+
+ case TextureScalarType.Texture2DDepthCompare:
+ case TextureScalarType.Texture2DLodLevelDepthCompare:
+ case TextureScalarType.Texture2DLodZeroDepthCompare:
+ return TextureType.Texture2D | TextureType.Shadow;
+
+ case TextureScalarType.Texture2DArray:
+ case TextureScalarType.Texture2DArrayLodZero:
+ return TextureType.Texture2D | TextureType.Array;
+
+ case TextureScalarType.Texture2DArrayLodZeroDepthCompare:
+ return TextureType.Texture2D | TextureType.Array | TextureType.Shadow;
+
+ case TextureScalarType.Texture3D:
+ case TextureScalarType.Texture3DLodZero:
+ return TextureType.Texture3D;
+
+ case TextureScalarType.TextureCube:
+ case TextureScalarType.TextureCubeLodLevel:
+ return TextureType.TextureCube;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+
+ private static TextureType GetTextureType(TexelLoadScalarType type)
+ {
+ switch (type)
+ {
+ case TexelLoadScalarType.Texture1DLodZero:
+ case TexelLoadScalarType.Texture1DLodLevel:
+ return TextureType.Texture1D;
+
+ case TexelLoadScalarType.Texture2DLodZero:
+ case TexelLoadScalarType.Texture2DLodZeroOffset:
+ case TexelLoadScalarType.Texture2DLodLevel:
+ case TexelLoadScalarType.Texture2DLodLevelOffset:
+ return TextureType.Texture2D;
+
+ case TexelLoadScalarType.Texture2DLodZeroMultisample:
+ return TextureType.Texture2D | TextureType.Multisample;
+
+ case TexelLoadScalarType.Texture3DLodZero:
+ return TextureType.Texture3D;
+
+ case TexelLoadScalarType.Texture2DArrayLodZero:
+ return TextureType.Texture2D | TextureType.Array;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+
+ private static TextureFlags GetTextureFlags(TextureScalarType type)
+ {
+ switch (type)
+ {
+ case TextureScalarType.Texture1DLodZero:
+ case TextureScalarType.Texture2DLodZero:
+ case TextureScalarType.Texture2DLodLevel:
+ case TextureScalarType.Texture2DLodLevelDepthCompare:
+ case TextureScalarType.Texture2DLodZeroDepthCompare:
+ case TextureScalarType.Texture2DArrayLodZero:
+ case TextureScalarType.Texture2DArrayLodZeroDepthCompare:
+ case TextureScalarType.Texture3DLodZero:
+ case TextureScalarType.TextureCubeLodLevel:
+ return TextureFlags.LodLevel;
+
+ case TextureScalarType.Texture2D:
+ case TextureScalarType.Texture2DDepthCompare:
+ case TextureScalarType.Texture2DArray:
+ case TextureScalarType.Texture3D:
+ case TextureScalarType.TextureCube:
+ return TextureFlags.None;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+
+ private static TextureFlags GetTextureFlags(TexelLoadScalarType type)
+ {
+ switch (type)
+ {
+ case TexelLoadScalarType.Texture1DLodZero:
+ case TexelLoadScalarType.Texture1DLodLevel:
+ case TexelLoadScalarType.Texture2DLodZero:
+ case TexelLoadScalarType.Texture2DLodLevel:
+ case TexelLoadScalarType.Texture2DLodZeroMultisample:
+ case TexelLoadScalarType.Texture3DLodZero:
+ case TexelLoadScalarType.Texture2DArrayLodZero:
+ return TextureFlags.LodLevel;
+
+ case TexelLoadScalarType.Texture2DLodZeroOffset:
+ case TexelLoadScalarType.Texture2DLodLevelOffset:
+ return TextureFlags.LodLevel | TextureFlags.Offset;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs
new file mode 100644
index 00000000..91c740b6
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs
@@ -0,0 +1,6 @@
+using Ryujinx.Graphics.Shader.Translation;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ delegate void InstEmitter(EmitterContext context);
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs b/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs
new file mode 100644
index 00000000..e55ed660
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs
@@ -0,0 +1,149 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static class Lop3Expression
+ {
+ public static Operand GetFromTruthTable(
+ EmitterContext context,
+ Operand srcA,
+ Operand srcB,
+ Operand srcC,
+ int imm)
+ {
+ Operand expr = null;
+
+ //Handle some simple cases, or cases where
+ //the KMap would yield poor results (like XORs).
+ if (imm == 0x96 || imm == 0x69)
+ {
+ //XOR (0x96) and XNOR (0x69).
+ if (imm == 0x69)
+ {
+ srcA = context.BitwiseNot(srcA);
+ }
+
+ expr = context.BitwiseExclusiveOr(srcA, srcB);
+ expr = context.BitwiseExclusiveOr(expr, srcC);
+
+ return expr;
+ }
+ else if (imm == 0)
+ {
+ //Always false.
+ return Const(IrConsts.False);
+ }
+ else if (imm == 0xff)
+ {
+ //Always true.
+ return Const(IrConsts.True);
+ }
+
+ int map;
+
+ //Encode into gray code.
+ map = ((imm >> 0) & 1) << 0;
+ map |= ((imm >> 1) & 1) << 4;
+ map |= ((imm >> 2) & 1) << 1;
+ map |= ((imm >> 3) & 1) << 5;
+ map |= ((imm >> 4) & 1) << 3;
+ map |= ((imm >> 5) & 1) << 7;
+ map |= ((imm >> 6) & 1) << 2;
+ map |= ((imm >> 7) & 1) << 6;
+
+ //Solve KMap, get sum of products.
+ int visited = 0;
+
+ for (int index = 0; index < 8 && visited != 0xff; index++)
+ {
+ if ((map & (1 << index)) == 0)
+ {
+ continue;
+ }
+
+ int mask = 0;
+
+ for (int mSize = 4; mSize != 0; mSize >>= 1)
+ {
+ mask = RotateLeft4((1 << mSize) - 1, index & 3) << (index & 4);
+
+ if ((map & mask) == mask)
+ {
+ break;
+ }
+ }
+
+ //The mask should wrap, if we are on the high row, shift to low etc.
+ int mask2 = (index & 4) != 0 ? mask >> 4 : mask << 4;
+
+ if ((map & mask2) == mask2)
+ {
+ mask |= mask2;
+ }
+
+ if ((mask & visited) == mask)
+ {
+ continue;
+ }
+
+ bool notA = (mask & 0x33) != 0;
+ bool notB = (mask & 0x99) != 0;
+ bool notC = (mask & 0x0f) != 0;
+
+ bool aChanges = (mask & 0xcc) != 0 && notA;
+ bool bChanges = (mask & 0x66) != 0 && notB;
+ bool cChanges = (mask & 0xf0) != 0 && notC;
+
+ Operand localExpr = null;
+
+ void And(Operand source)
+ {
+ if (localExpr != null)
+ {
+ localExpr = context.BitwiseAnd(localExpr, source);
+ }
+ else
+ {
+ localExpr = source;
+ }
+ }
+
+ if (!aChanges)
+ {
+ And(context.BitwiseNot(srcA, notA));
+ }
+
+ if (!bChanges)
+ {
+ And(context.BitwiseNot(srcB, notB));
+ }
+
+ if (!cChanges)
+ {
+ And(context.BitwiseNot(srcC, notC));
+ }
+
+ if (expr != null)
+ {
+ expr = context.BitwiseOr(expr, localExpr);
+ }
+ else
+ {
+ expr = localExpr;
+ }
+
+ visited |= mask;
+ }
+
+ return expr;
+ }
+
+ private static int RotateLeft4(int value, int shift)
+ {
+ return ((value << shift) | (value >> (4 - shift))) & 0xf;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs
new file mode 100644
index 00000000..94975337
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class BasicBlock
+ {
+ public int Index { get; set; }
+
+ public LinkedList<INode> Operations { get; }
+
+ private BasicBlock _next;
+ private BasicBlock _branch;
+
+ public BasicBlock Next
+ {
+ get => _next;
+ set => _next = AddSuccessor(_next, value);
+ }
+
+ public BasicBlock Branch
+ {
+ get => _branch;
+ set => _branch = AddSuccessor(_branch, value);
+ }
+
+ public bool HasBranch => _branch != null;
+
+ public List<BasicBlock> Predecessors { get; }
+
+ public HashSet<BasicBlock> DominanceFrontiers { get; }
+
+ public BasicBlock ImmediateDominator { get; set; }
+
+ public BasicBlock()
+ {
+ Operations = new LinkedList<INode>();
+
+ Predecessors = new List<BasicBlock>();
+
+ DominanceFrontiers = new HashSet<BasicBlock>();
+ }
+
+ 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 00000000..48dda24b
--- /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 00000000..ac0ebc2b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs
@@ -0,0 +1,87 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ [Flags]
+ enum Instruction
+ {
+ Absolute = 1,
+ Add,
+ BitfieldExtractS32,
+ BitfieldExtractU32,
+ BitfieldInsert,
+ BitfieldReverse,
+ BitwiseAnd,
+ BitwiseExclusiveOr,
+ BitwiseNot,
+ BitwiseOr,
+ Branch,
+ BranchIfFalse,
+ BranchIfTrue,
+ Ceiling,
+ Clamp,
+ ClampU32,
+ CompareEqual,
+ CompareGreater,
+ CompareGreaterOrEqual,
+ CompareGreaterOrEqualU32,
+ CompareGreaterU32,
+ CompareLess,
+ CompareLessOrEqual,
+ CompareLessOrEqualU32,
+ CompareLessU32,
+ CompareNotEqual,
+ ConditionalSelect,
+ ConvertFPToS32,
+ ConvertS32ToFP,
+ ConvertU32ToFP,
+ Copy,
+ Cosine,
+ Discard,
+ Divide,
+ EmitVertex,
+ EndPrimitive,
+ ExponentB2,
+ Floor,
+ FusedMultiplyAdd,
+ IsNan,
+ LoadConstant,
+ LoadGlobal,
+ LoadLocal,
+ LogarithmB2,
+ LogicalAnd,
+ LogicalExclusiveOr,
+ LogicalNot,
+ LogicalOr,
+ LoopBreak,
+ LoopContinue,
+ MarkLabel,
+ Maximum,
+ MaximumU32,
+ Minimum,
+ MinimumU32,
+ Multiply,
+ Negate,
+ PackDouble2x32,
+ PackHalf2x16,
+ ReciprocalSquareRoot,
+ Return,
+ ShiftLeft,
+ ShiftRightS32,
+ ShiftRightU32,
+ Sine,
+ SquareRoot,
+ StoreGlobal,
+ StoreLocal,
+ Subtract,
+ TextureSample,
+ TextureSize,
+ Truncate,
+ UnpackDouble2x32,
+ UnpackHalf2x16,
+
+ Count,
+ FP = 1 << 16,
+ Mask = 0xffff
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs
new file mode 100644
index 00000000..c264e47d
--- /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 00000000..1df88a3d
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs
@@ -0,0 +1,79 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class Operand
+ {
+ private const int CbufSlotBits = 5;
+ private const int CbufSlotLsb = 32 - CbufSlotBits;
+ private const int CbufSlotMask = (1 << CbufSlotBits) - 1;
+
+ public OperandType Type { get; }
+
+ public int Value { get; }
+
+ public INode AsgOp { get; set; }
+
+ public HashSet<INode> UseOps { get; }
+
+ private Operand()
+ {
+ UseOps = new HashSet<INode>();
+ }
+
+ public Operand(OperandType type) : this()
+ {
+ Type = type;
+ }
+
+ public Operand(OperandType type, int value) : this()
+ {
+ Type = type;
+ Value = value;
+ }
+
+ public Operand(Register reg) : this()
+ {
+ Type = OperandType.Register;
+ Value = PackRegInfo(reg.Index, reg.Type);
+ }
+
+ public Operand(int slot, int offset) : this()
+ {
+ Type = OperandType.ConstantBuffer;
+ Value = PackCbufInfo(slot, offset);
+ }
+
+ private static int PackCbufInfo(int slot, int offset)
+ {
+ return (slot << CbufSlotLsb) | offset;
+ }
+
+ private static int PackRegInfo(int index, RegisterType type)
+ {
+ return ((int)type << 24) | index;
+ }
+
+ public int GetCbufSlot()
+ {
+ return (Value >> CbufSlotLsb) & CbufSlotMask;
+ }
+
+ public int GetCbufOffset()
+ {
+ return Value & ~(CbufSlotMask << CbufSlotLsb);
+ }
+
+ public Register GetRegister()
+ {
+ return new Register(Value & 0xffffff, (RegisterType)(Value >> 24));
+ }
+
+ public float AsFloat()
+ {
+ return BitConverter.Int32BitsToSingle(Value);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs
new file mode 100644
index 00000000..6765f8a4
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs
@@ -0,0 +1,62 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using System;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ static class OperandHelper
+ {
+ public static Operand Attribute(int value)
+ {
+ return new Operand(OperandType.Attribute, value);
+ }
+
+ public static Operand Cbuf(int slot, int offset)
+ {
+ return new Operand(slot, offset);
+ }
+
+ public static Operand Const(int value)
+ {
+ return new Operand(OperandType.Constant, value);
+ }
+
+ public static Operand ConstF(float value)
+ {
+ return new Operand(OperandType.Constant, BitConverter.SingleToInt32Bits(value));
+ }
+
+ public static Operand Label()
+ {
+ return new Operand(OperandType.Label);
+ }
+
+ public static Operand Local()
+ {
+ return new Operand(OperandType.LocalVariable);
+ }
+
+ public static Operand Register(int index, RegisterType type)
+ {
+ return Register(new Register(index, type));
+ }
+
+ public static Operand Register(Register reg)
+ {
+ if (reg.IsRZ)
+ {
+ return Const(0);
+ }
+ else if (reg.IsPT)
+ {
+ return Const(IrConsts.True);
+ }
+
+ return new Operand(reg);
+ }
+
+ public static Operand Undef()
+ {
+ return new Operand(OperandType.Undefined);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs
new file mode 100644
index 00000000..e0e2a667
--- /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 00000000..f6579953
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs
@@ -0,0 +1,101 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class Operation : INode
+ {
+ public Instruction Inst { get; private set; }
+
+ private Operand _dest;
+
+ public Operand Dest
+ {
+ get => _dest;
+ set => _dest = AssignDest(value);
+ }
+
+ private Operand[] _sources;
+
+ public int SourcesCount => _sources.Length;
+
+ public int ComponentIndex { get; }
+
+ public Operation(Instruction inst, Operand dest, params Operand[] sources)
+ {
+ Inst = inst;
+ Dest = dest;
+
+ //The array may be modified externally, so we store a copy.
+ _sources = (Operand[])sources.Clone();
+
+ for (int index = 0; index < _sources.Length; index++)
+ {
+ Operand source = _sources[index];
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+ }
+ }
+
+ public Operation(
+ Instruction inst,
+ int compIndex,
+ Operand dest,
+ params Operand[] sources) : this(inst, dest, sources)
+ {
+ ComponentIndex = compIndex;
+ }
+
+ private Operand AssignDest(Operand dest)
+ {
+ if (dest != null && dest.Type == OperandType.LocalVariable)
+ {
+ dest.AsgOp = this;
+ }
+
+ return dest;
+ }
+
+ public Operand GetSource(int index)
+ {
+ return _sources[index];
+ }
+
+ public void SetSource(int index, Operand source)
+ {
+ Operand oldSrc = _sources[index];
+
+ if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable)
+ {
+ oldSrc.UseOps.Remove(this);
+ }
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+
+ _sources[index] = source;
+ }
+
+ public void TurnIntoCopy(Operand source)
+ {
+ Inst = Instruction.Copy;
+
+ foreach (Operand oldSrc in _sources)
+ {
+ if (oldSrc.Type == OperandType.LocalVariable)
+ {
+ oldSrc.UseOps.Remove(this);
+ }
+ }
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+
+ _sources = new Operand[] { source };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs
new file mode 100644
index 00000000..13ff41bd
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs
@@ -0,0 +1,94 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class PhiNode : INode
+ {
+ private Operand _dest;
+
+ public Operand Dest
+ {
+ get => _dest;
+ set => _dest = AssignDest(value);
+ }
+
+ private HashSet<BasicBlock> _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<PhiSource> _sources;
+
+ public int SourcesCount => _sources.Count;
+
+ public PhiNode(Operand dest)
+ {
+ _blocks = new HashSet<BasicBlock>();
+
+ _sources = new List<PhiSource>();
+
+ dest.AsgOp = this;
+
+ Dest = dest;
+ }
+
+ private Operand AssignDest(Operand dest)
+ {
+ if (dest != null && dest.Type == OperandType.LocalVariable)
+ {
+ dest.AsgOp = this;
+ }
+
+ return dest;
+ }
+
+ public void AddSource(BasicBlock block, Operand operand)
+ {
+ if (_blocks.Add(block))
+ {
+ if (operand.Type == OperandType.LocalVariable)
+ {
+ operand.UseOps.Add(this);
+ }
+
+ _sources.Add(new PhiSource(block, operand));
+ }
+ }
+
+ public Operand GetSource(int index)
+ {
+ return _sources[index].Operand;
+ }
+
+ public BasicBlock GetBlock(int index)
+ {
+ return _sources[index].Block;
+ }
+
+ public void SetSource(int index, Operand source)
+ {
+ Operand oldSrc = _sources[index].Operand;
+
+ if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable)
+ {
+ oldSrc.UseOps.Remove(this);
+ }
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+
+ _sources[index].Operand = source;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs
new file mode 100644
index 00000000..5f0a8427
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ [Flags]
+ enum TextureFlags
+ {
+ None = 0,
+ Bindless = 1 << 0,
+ Gather = 1 << 1,
+ IntCoords = 1 << 2,
+ LodBias = 1 << 3,
+ LodLevel = 1 << 4,
+ Offset = 1 << 5,
+ Offsets = 1 << 6
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs
new file mode 100644
index 00000000..f5f2cc5c
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class TextureOperation : Operation
+ {
+ public TextureType Type { get; }
+ public TextureFlags Flags { get; }
+
+ public int Handle { get; }
+
+ public TextureOperation(
+ Instruction inst,
+ TextureType type,
+ TextureFlags flags,
+ int handle,
+ int compIndex,
+ Operand dest,
+ params Operand[] sources) : base(inst, compIndex, dest, sources)
+ {
+ Type = type;
+ Flags = flags;
+ Handle = handle;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs
new file mode 100644
index 00000000..bf207007
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ [Flags]
+ enum TextureType
+ {
+ Texture1D,
+ Texture2D,
+ Texture3D,
+ TextureCube,
+
+ Mask = 0xff,
+
+ Array = 1 << 8,
+ Multisample = 1 << 9,
+ Shadow = 1 << 10
+ }
+
+ static class TextureTypeExtensions
+ {
+ public static int GetCoordsCount(this TextureType type)
+ {
+ switch (type & TextureType.Mask)
+ {
+ case TextureType.Texture1D: return 1;
+ case TextureType.Texture2D: return 2;
+ case TextureType.Texture3D: return 3;
+ case TextureType.TextureCube: return 3;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/ShaderConfig.cs b/Ryujinx.Graphics/Shader/ShaderConfig.cs
new file mode 100644
index 00000000..c2a94814
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/ShaderConfig.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Graphics.Gal;
+using System;
+
+namespace Ryujinx.Graphics.Shader
+{
+ public struct ShaderConfig
+ {
+ public GalShaderType Type { get; }
+
+ public int MaxCBufferSize;
+
+ public ShaderConfig(GalShaderType type, int maxCBufferSize)
+ {
+ if (maxCBufferSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(maxCBufferSize));
+ }
+
+ Type = type;
+ MaxCBufferSize = maxCBufferSize;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/ShaderHeader.cs b/Ryujinx.Graphics/Shader/ShaderHeader.cs
new file mode 100644
index 00000000..53abdc56
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/ShaderHeader.cs
@@ -0,0 +1,166 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.Decoders;
+using System;
+
+namespace Ryujinx.Graphics.Shader
+{
+ struct OutputMapTarget
+ {
+ public bool Red { get; }
+ public bool Green { get; }
+ public bool Blue { get; }
+ public bool Alpha { get; }
+
+ public bool Enabled => Red || Green || Blue || Alpha;
+
+ public OutputMapTarget(bool red, bool green, bool blue, bool alpha)
+ {
+ Red = red;
+ Green = green;
+ Blue = blue;
+ Alpha = alpha;
+ }
+
+ public bool ComponentEnabled(int component)
+ {
+ switch (component)
+ {
+ case 0: return Red;
+ case 1: return Green;
+ case 2: return Blue;
+ case 3: return Alpha;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(component));
+ }
+ }
+
+ class ShaderHeader
+ {
+ public int SphType { get; }
+
+ public int Version { get; }
+
+ public int ShaderType { get; }
+
+ public bool MrtEnable { get; }
+
+ public bool KillsPixels { get; }
+
+ public bool DoesGlobalStore { get; }
+
+ public int SassVersion { get; }
+
+ public bool DoesLoadOrStore { get; }
+
+ public bool DoesFp64 { get; }
+
+ public int StreamOutMask{ get; }
+
+ public int ShaderLocalMemoryLowSize { get; }
+
+ public int PerPatchAttributeCount { get; }
+
+ public int ShaderLocalMemoryHighSize { get; }
+
+ public int ThreadsPerInputPrimitive { get; }
+
+ public int ShaderLocalMemoryCrsSize { get; }
+
+ public int OutputTopology { get; }
+
+ public int MaxOutputVertexCount { get; }
+
+ public int StoreReqStart { get; }
+ public int StoreReqEnd { get; }
+
+ public OutputMapTarget[] OmapTargets { get; }
+ public bool OmapSampleMask { get; }
+ public bool OmapDepth { get; }
+
+ public ShaderHeader(IGalMemory memory, ulong address)
+ {
+ int commonWord0 = memory.ReadInt32((long)address + 0);
+ int commonWord1 = memory.ReadInt32((long)address + 4);
+ int commonWord2 = memory.ReadInt32((long)address + 8);
+ int commonWord3 = memory.ReadInt32((long)address + 12);
+ int commonWord4 = memory.ReadInt32((long)address + 16);
+
+ SphType = commonWord0.Extract(0, 5);
+
+ Version = commonWord0.Extract(5, 5);
+
+ ShaderType = commonWord0.Extract(10, 4);
+
+ MrtEnable = commonWord0.Extract(14);
+
+ KillsPixels = commonWord0.Extract(15);
+
+ DoesGlobalStore = commonWord0.Extract(16);
+
+ SassVersion = commonWord0.Extract(17, 4);
+
+ DoesLoadOrStore = commonWord0.Extract(26);
+
+ DoesFp64 = commonWord0.Extract(27);
+
+ StreamOutMask = commonWord0.Extract(28, 4);
+
+ ShaderLocalMemoryLowSize = commonWord1.Extract(0, 24);
+
+ PerPatchAttributeCount = commonWord1.Extract(24, 8);
+
+ ShaderLocalMemoryHighSize = commonWord2.Extract(0, 24);
+
+ ThreadsPerInputPrimitive = commonWord2.Extract(24, 8);
+
+ ShaderLocalMemoryCrsSize = commonWord3.Extract(0, 24);
+
+ OutputTopology = commonWord3.Extract(24, 4);
+
+ MaxOutputVertexCount = commonWord4.Extract(0, 12);
+
+ StoreReqStart = commonWord4.Extract(12, 8);
+ StoreReqEnd = commonWord4.Extract(24, 8);
+
+ int type2OmapTarget = memory.ReadInt32((long)address + 72);
+ int type2Omap = memory.ReadInt32((long)address + 76);
+
+ OmapTargets = new OutputMapTarget[8];
+
+ for (int offset = 0; offset < OmapTargets.Length * 4; offset += 4)
+ {
+ OmapTargets[offset >> 2] = new OutputMapTarget(
+ type2OmapTarget.Extract(offset + 0),
+ type2OmapTarget.Extract(offset + 1),
+ type2OmapTarget.Extract(offset + 2),
+ type2OmapTarget.Extract(offset + 3));
+ }
+
+ OmapSampleMask = type2Omap.Extract(0);
+ OmapDepth = type2Omap.Extract(1);
+ }
+
+ public int DepthRegister
+ {
+ get
+ {
+ int count = 0;
+
+ for (int index = 0; index < OmapTargets.Length; index++)
+ {
+ for (int component = 0; component < 4; component++)
+ {
+ if (OmapTargets[index].ComponentEnabled(component))
+ {
+ count++;
+ }
+ }
+ }
+
+ //Depth register is always two registers after the last color output.
+ return count + 1;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/ShaderProgram.cs b/Ryujinx.Graphics/Shader/ShaderProgram.cs
new file mode 100644
index 00000000..9257fd26
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/ShaderProgram.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Shader
+{
+ public class ShaderProgram
+ {
+ public ShaderProgramInfo Info { get; }
+
+ public string Code { get; }
+
+ internal ShaderProgram(ShaderProgramInfo info, string code)
+ {
+ Info = info;
+ Code = code;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs
new file mode 100644
index 00000000..c529a353
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.ObjectModel;
+
+namespace Ryujinx.Graphics.Shader
+{
+ public class ShaderProgramInfo
+ {
+ public ReadOnlyCollection<CBufferDescriptor> CBuffers { get; }
+ public ReadOnlyCollection<TextureDescriptor> Textures { get; }
+
+ internal ShaderProgramInfo(CBufferDescriptor[] cBuffers, TextureDescriptor[] textures)
+ {
+ CBuffers = Array.AsReadOnly(cBuffers);
+ Textures = Array.AsReadOnly(textures);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs
new file mode 100644
index 00000000..bb3fe7af
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs
@@ -0,0 +1,35 @@
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstAssignment : AstNode
+ {
+ public IAstNode Destination { get; }
+
+ private IAstNode _source;
+
+ public IAstNode Source
+ {
+ get
+ {
+ return _source;
+ }
+ set
+ {
+ RemoveUse(_source, this);
+
+ AddUse(value, this);
+
+ _source = value;
+ }
+ }
+
+ public AstAssignment(IAstNode destination, IAstNode source)
+ {
+ Destination = destination;
+ Source = source;
+
+ AddDef(destination, this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs
new file mode 100644
index 00000000..fdef87de
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs
@@ -0,0 +1,116 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstBlock : AstNode, IEnumerable<IAstNode>
+ {
+ public AstBlockType Type { get; private set; }
+
+ private IAstNode _condition;
+
+ public IAstNode Condition
+ {
+ get
+ {
+ return _condition;
+ }
+ set
+ {
+ RemoveUse(_condition, this);
+
+ AddUse(value, this);
+
+ _condition = value;
+ }
+ }
+
+ private LinkedList<IAstNode> _nodes;
+
+ public IAstNode First => _nodes.First?.Value;
+
+ public int Count => _nodes.Count;
+
+ public AstBlock(AstBlockType type, IAstNode condition = null)
+ {
+ Type = type;
+ Condition = condition;
+
+ _nodes = new LinkedList<IAstNode>();
+ }
+
+ public void Add(IAstNode node)
+ {
+ Add(node, _nodes.AddLast(node));
+ }
+
+ public void AddFirst(IAstNode node)
+ {
+ Add(node, _nodes.AddFirst(node));
+ }
+
+ public void AddBefore(IAstNode next, IAstNode node)
+ {
+ Add(node, _nodes.AddBefore(next.LLNode, node));
+ }
+
+ public void AddAfter(IAstNode prev, IAstNode node)
+ {
+ Add(node, _nodes.AddAfter(prev.LLNode, node));
+ }
+
+ private void Add(IAstNode node, LinkedListNode<IAstNode> newNode)
+ {
+ if (node.Parent != null)
+ {
+ throw new ArgumentException("Node already belongs to a block.");
+ }
+
+ node.Parent = this;
+ node.LLNode = newNode;
+ }
+
+ public void Remove(IAstNode node)
+ {
+ _nodes.Remove(node.LLNode);
+
+ node.Parent = null;
+ node.LLNode = null;
+ }
+
+ public void AndCondition(IAstNode cond)
+ {
+ Condition = new AstOperation(Instruction.LogicalAnd, Condition, cond);
+ }
+
+ public void OrCondition(IAstNode cond)
+ {
+ Condition = new AstOperation(Instruction.LogicalOr, Condition, cond);
+ }
+ public void TurnIntoIf(IAstNode cond)
+ {
+ Condition = cond;
+
+ Type = AstBlockType.If;
+ }
+
+ public void TurnIntoElseIf()
+ {
+ Type = AstBlockType.ElseIf;
+ }
+
+ public IEnumerator<IAstNode> GetEnumerator()
+ {
+ return _nodes.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs
new file mode 100644
index 00000000..c12efda9
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ enum AstBlockType
+ {
+ DoWhile,
+ If,
+ Else,
+ ElseIf,
+ Main,
+ While
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs
new file mode 100644
index 00000000..9397fdb9
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstBlockVisitor
+ {
+ public AstBlock Block { get; private set; }
+
+ public class BlockVisitationEventArgs : EventArgs
+ {
+ public AstBlock Block { get; }
+
+ public BlockVisitationEventArgs(AstBlock block)
+ {
+ Block = block;
+ }
+ }
+
+ public event EventHandler<BlockVisitationEventArgs> BlockEntered;
+ public event EventHandler<BlockVisitationEventArgs> BlockLeft;
+
+ public AstBlockVisitor(AstBlock mainBlock)
+ {
+ Block = mainBlock;
+ }
+
+ public IEnumerable<IAstNode> Visit()
+ {
+ IAstNode node = Block.First;
+
+ while (node != null)
+ {
+ //We reached a child block, visit the nodes inside.
+ while (node is AstBlock childBlock)
+ {
+ Block = childBlock;
+
+ node = childBlock.First;
+
+ BlockEntered?.Invoke(this, new BlockVisitationEventArgs(Block));
+ }
+
+ //Node may be null, if the block is empty.
+ if (node != null)
+ {
+ IAstNode next = Next(node);
+
+ yield return node;
+
+ node = next;
+ }
+
+ //We reached the end of the list, go up on tree to the parent blocks.
+ while (node == null && Block.Type != AstBlockType.Main)
+ {
+ BlockLeft?.Invoke(this, new BlockVisitationEventArgs(Block));
+
+ node = Next(Block);
+
+ Block = Block.Parent;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs
new file mode 100644
index 00000000..9d3148e1
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs
@@ -0,0 +1,73 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class AstHelper
+ {
+ public static void AddUse(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Uses.Add(parent);
+ }
+ }
+
+ public static void AddDef(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Defs.Add(parent);
+ }
+ }
+
+ public static void RemoveUse(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Uses.Remove(parent);
+ }
+ }
+
+ public static void RemoveDef(IAstNode node, IAstNode parent)
+ {
+ if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
+ {
+ operand.Defs.Remove(parent);
+ }
+ }
+
+ public static AstAssignment Assign(IAstNode destination, IAstNode source)
+ {
+ return new AstAssignment(destination, source);
+ }
+
+ public static AstOperand Const(int value)
+ {
+ return new AstOperand(OperandType.Constant, value);
+ }
+
+ public static AstOperand Local(VariableType type)
+ {
+ AstOperand local = new AstOperand(OperandType.LocalVariable);
+
+ local.VarType = type;
+
+ return local;
+ }
+
+ public static IAstNode InverseCond(IAstNode cond)
+ {
+ return new AstOperation(Instruction.LogicalNot, cond);
+ }
+
+ public static IAstNode Next(IAstNode node)
+ {
+ return node.LLNode.Next?.Value;
+ }
+
+ public static IAstNode Previous(IAstNode node)
+ {
+ return node.LLNode.Previous?.Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs
new file mode 100644
index 00000000..c667aac9
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstNode : IAstNode
+ {
+ public AstBlock Parent { get; set; }
+
+ public LinkedListNode<IAstNode> LLNode { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs
new file mode 100644
index 00000000..97ff3ca9
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstOperand : AstNode
+ {
+ public HashSet<IAstNode> Defs { get; }
+ public HashSet<IAstNode> Uses { get; }
+
+ public OperandType Type { get; }
+
+ public VariableType VarType { get; set; }
+
+ public int Value { get; }
+
+ public int CbufSlot { get; }
+ public int CbufOffset { get; }
+
+ private AstOperand()
+ {
+ Defs = new HashSet<IAstNode>();
+ Uses = new HashSet<IAstNode>();
+
+ VarType = VariableType.S32;
+ }
+
+ public AstOperand(Operand operand) : this()
+ {
+ Type = operand.Type;
+
+ if (Type == OperandType.ConstantBuffer)
+ {
+ CbufSlot = operand.GetCbufSlot();
+ CbufOffset = operand.GetCbufOffset();
+ }
+ else
+ {
+ Value = operand.Value;
+ }
+ }
+
+ public AstOperand(OperandType type, int value = 0) : this()
+ {
+ Type = type;
+ Value = value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs
new file mode 100644
index 00000000..1607ffec
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstOperation : AstNode
+ {
+ public Instruction Inst { get; }
+
+ public int ComponentMask { get; }
+
+ private IAstNode[] _sources;
+
+ public int SourcesCount => _sources.Length;
+
+ public AstOperation(Instruction inst, params IAstNode[] sources)
+ {
+ Inst = inst;
+ _sources = sources;
+
+ foreach (IAstNode source in sources)
+ {
+ AddUse(source, this);
+ }
+
+ ComponentMask = 1;
+ }
+
+ public AstOperation(Instruction inst, int compMask, params IAstNode[] sources) : this(inst, sources)
+ {
+ ComponentMask = compMask;
+ }
+
+ public IAstNode GetSource(int index)
+ {
+ return _sources[index];
+ }
+
+ public void SetSource(int index, IAstNode source)
+ {
+ RemoveUse(_sources[index], this);
+
+ AddUse(source, this);
+
+ _sources[index] = source;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs
new file mode 100644
index 00000000..0f5392b7
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs
@@ -0,0 +1,149 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+using System.Linq;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class AstOptimizer
+ {
+ public static void Optimize(StructuredProgramInfo info)
+ {
+ AstBlock mainBlock = info.MainBlock;
+
+ AstBlockVisitor visitor = new AstBlockVisitor(mainBlock);
+
+ foreach (IAstNode node in visitor.Visit())
+ {
+ if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar)
+ {
+ bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source);
+
+ if (propVar.Defs.Count == 1 && isWorthPropagating)
+ {
+ PropagateExpression(propVar, assignment.Source);
+ }
+
+ if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0)
+ {
+ visitor.Block.Remove(assignment);
+
+ info.Locals.Remove(propVar);
+ }
+ }
+ }
+
+ RemoveEmptyBlocks(mainBlock);
+ }
+
+ private static bool IsWorthPropagating(IAstNode source)
+ {
+ if (!(source is AstOperation srcOp))
+ {
+ return false;
+ }
+
+ if (!InstructionInfo.IsUnary(srcOp.Inst))
+ {
+ return false;
+ }
+
+ return srcOp.GetSource(0) is AstOperand || srcOp.Inst == Instruction.Copy;
+ }
+
+ private static void PropagateExpression(AstOperand propVar, IAstNode source)
+ {
+ IAstNode[] uses = propVar.Uses.ToArray();
+
+ foreach (IAstNode useNode in uses)
+ {
+ if (useNode is AstBlock useBlock)
+ {
+ useBlock.Condition = source;
+ }
+ else if (useNode is AstOperation useOperation)
+ {
+ for (int srcIndex = 0; srcIndex < useOperation.SourcesCount; srcIndex++)
+ {
+ if (useOperation.GetSource(srcIndex) == propVar)
+ {
+ useOperation.SetSource(srcIndex, source);
+ }
+ }
+ }
+ else if (useNode is AstAssignment useAssignment)
+ {
+ useAssignment.Source = source;
+ }
+ }
+ }
+
+ private static void RemoveEmptyBlocks(AstBlock mainBlock)
+ {
+ Queue<AstBlock> pending = new Queue<AstBlock>();
+
+ pending.Enqueue(mainBlock);
+
+ while (pending.TryDequeue(out AstBlock block))
+ {
+ foreach (IAstNode node in block)
+ {
+ if (node is AstBlock childBlock)
+ {
+ pending.Enqueue(childBlock);
+ }
+ }
+
+ AstBlock parent = block.Parent;
+
+ if (parent == null)
+ {
+ continue;
+ }
+
+ AstBlock nextBlock = Next(block) as AstBlock;
+
+ bool hasElse = nextBlock != null && nextBlock.Type == AstBlockType.Else;
+
+ bool isIf = block.Type == AstBlockType.If;
+
+ if (block.Count == 0)
+ {
+ if (isIf)
+ {
+ if (hasElse)
+ {
+ nextBlock.TurnIntoIf(InverseCond(block.Condition));
+ }
+
+ parent.Remove(block);
+ }
+ else if (block.Type == AstBlockType.Else)
+ {
+ parent.Remove(block);
+ }
+ }
+ else if (isIf && parent.Type == AstBlockType.Else && parent.Count == (hasElse ? 2 : 1))
+ {
+ AstBlock parentOfParent = parent.Parent;
+
+ parent.Remove(block);
+
+ parentOfParent.AddAfter(parent, block);
+
+ if (hasElse)
+ {
+ parent.Remove(nextBlock);
+
+ parentOfParent.AddAfter(block, nextBlock);
+ }
+
+ parentOfParent.Remove(parent);
+
+ block.TurnIntoElseIf();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs
new file mode 100644
index 00000000..e40f7b70
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class AstTextureOperation : AstOperation
+ {
+ public TextureType Type { get; }
+ public TextureFlags Flags { get; }
+
+ public int Handle { get; }
+
+ public AstTextureOperation(
+ Instruction inst,
+ TextureType type,
+ TextureFlags flags,
+ int handle,
+ int compMask,
+ params IAstNode[] sources) : base(inst, compMask, sources)
+ {
+ Type = type;
+ Flags = flags;
+ Handle = handle;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs b/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs
new file mode 100644
index 00000000..dffc3142
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs
@@ -0,0 +1,459 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class GotoElimination
+ {
+ //This is a modified version of the algorithm presented on the paper
+ //"Taming Control Flow: A Structured Approach to Eliminating Goto Statements".
+ public static void Eliminate(GotoStatement[] gotos)
+ {
+ for (int index = gotos.Length - 1; index >= 0; index--)
+ {
+ GotoStatement stmt = gotos[index];
+
+ AstBlock gBlock = ParentBlock(stmt.Goto);
+ AstBlock lBlock = ParentBlock(stmt.Label);
+
+ int gLevel = Level(gBlock);
+ int lLevel = Level(lBlock);
+
+ if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel))
+ {
+ AstBlock drBlock = gBlock;
+
+ int drLevel = gLevel;
+
+ do
+ {
+ drBlock = drBlock.Parent;
+
+ drLevel--;
+ }
+ while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel));
+
+ MoveOutward(stmt, gLevel, drLevel);
+
+ gBlock = drBlock;
+ gLevel = drLevel;
+
+ if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else)
+ {
+ //It's possible that the label was enclosed inside an else block,
+ //in this case we need to update the block and level.
+ //We also need to set the IsLoop for the case when the label is
+ //now before the goto, due to the newly introduced else block.
+ lBlock = ParentBlock(stmt.Label);
+
+ lLevel = Level(lBlock);
+
+ if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel))
+ {
+ stmt.IsLoop = true;
+ }
+ }
+ }
+
+ if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel))
+ {
+ if (gLevel > lLevel)
+ {
+ MoveOutward(stmt, gLevel, lLevel);
+ }
+ else
+ {
+ if (stmt.IsLoop)
+ {
+ Lift(stmt);
+ }
+
+ MoveInward(stmt);
+ }
+ }
+
+ gBlock = ParentBlock(stmt.Goto);
+
+ if (stmt.IsLoop)
+ {
+ EncloseDoWhile(stmt, gBlock, stmt.Label);
+ }
+ else
+ {
+ Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label);
+ }
+
+ gBlock.Remove(stmt.Goto);
+ }
+ }
+
+ private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel)
+ {
+ return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel));
+ }
+
+ private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel)
+ {
+ //If the levels are equal, they can be either siblings or indirectly related.
+ if (lLevel == rLevel)
+ {
+ return false;
+ }
+
+ IAstNode block;
+ IAstNode other;
+
+ int blockLvl, otherLvl;
+
+ if (lLevel > rLevel)
+ {
+ block = lBlock;
+ blockLvl = lLevel;
+ other = rBlock;
+ otherLvl = rLevel;
+ }
+ else /* if (rLevel > lLevel) */
+ {
+ block = rBlock;
+ blockLvl = rLevel;
+ other = lBlock;
+ otherLvl = lLevel;
+ }
+
+ while (blockLvl >= otherLvl)
+ {
+ if (block == other)
+ {
+ return true;
+ }
+
+ block = block.Parent;
+
+ blockLvl--;
+ }
+
+ return false;
+ }
+
+ private static void Lift(GotoStatement stmt)
+ {
+ AstBlock block = ParentBlock(stmt.Goto);
+
+ AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label));
+
+ AstBlock loopFirstStmt = path[path.Length - 1];
+
+ if (loopFirstStmt.Type == AstBlockType.Else)
+ {
+ loopFirstStmt = Previous(loopFirstStmt) as AstBlock;
+
+ if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If)
+ {
+ throw new InvalidOperationException("Found an else without a matching if.");
+ }
+ }
+
+ AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt);
+
+ block.Remove(stmt.Goto);
+
+ newBlock.AddFirst(stmt.Goto);
+
+ stmt.IsLoop = false;
+ }
+
+ private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel)
+ {
+ AstBlock origin = ParentBlock(stmt.Goto);
+
+ AstBlock block = origin;
+
+ //Check if a loop is enclosing the goto, and the block that is
+ //directly related to the label is above the loop block.
+ //In that case, we need to introduce a break to get out of the loop.
+ AstBlock loopBlock = origin;
+
+ int loopLevel = gLevel;
+
+ while (loopLevel > lLevel)
+ {
+ AstBlock child = loopBlock;
+
+ loopBlock = loopBlock.Parent;
+
+ loopLevel--;
+
+ if (child.Type == AstBlockType.DoWhile)
+ {
+ EncloseSingleInst(stmt, Instruction.LoopBreak);
+
+ block.Remove(stmt.Goto);
+
+ loopBlock.AddAfter(child, stmt.Goto);
+
+ block = loopBlock;
+ gLevel = loopLevel;
+ }
+ }
+
+ //Insert ifs to skip the parts that shouldn't be executed due to the goto.
+ bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If;
+
+ while (gLevel > lLevel)
+ {
+ Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto));
+
+ block.Remove(stmt.Goto);
+
+ AstBlock child = block;
+
+ //We can't move the goto in the middle of a if and a else block, in
+ //this case we need to move it after the else.
+ //IsLoop may need to be updated if the label is inside the else, as
+ //introducing a loop is the only way to ensure the else will be executed.
+ if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else)
+ {
+ child = elseBlock;
+ }
+
+ block = block.Parent;
+
+ block.AddAfter(child, stmt.Goto);
+
+ gLevel--;
+
+ if (tryInsertElse && child == origin)
+ {
+ AstBlock lBlock = ParentBlock(stmt.Label);
+
+ IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null;
+
+ AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last);
+
+ if (newBlock != null)
+ {
+ block.Remove(stmt.Goto);
+
+ block.AddAfter(newBlock, stmt.Goto);
+ }
+ }
+ }
+ }
+
+ private static void MoveInward(GotoStatement stmt)
+ {
+ AstBlock block = ParentBlock(stmt.Goto);
+
+ AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label));
+
+ for (int index = path.Length - 1; index >= 0; index--)
+ {
+ AstBlock child = path[index];
+ AstBlock last = child;
+
+ if (child.Type == AstBlockType.If)
+ {
+ //Modify the if condition to allow it to be entered by the goto.
+ if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition))
+ {
+ child.OrCondition(stmt.Condition);
+ }
+ }
+ else if (child.Type == AstBlockType.Else)
+ {
+ //Modify the matching if condition to force the else to be entered by the goto.
+ if (!(Previous(child) is AstBlock ifBlock) || ifBlock.Type != AstBlockType.If)
+ {
+ throw new InvalidOperationException("Found an else without a matching if.");
+ }
+
+ IAstNode cond = InverseCond(stmt.Condition);
+
+ if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond))
+ {
+ ifBlock.AndCondition(cond);
+ }
+
+ last = ifBlock;
+ }
+
+ Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last);
+
+ block.Remove(stmt.Goto);
+
+ child.AddFirst(stmt.Goto);
+
+ block = child;
+ }
+ }
+
+ private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond)
+ {
+ while (node is AstOperation operation && operation.SourcesCount == 2)
+ {
+ if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond))
+ {
+ return true;
+ }
+
+ node = operation.GetSource(0);
+ }
+
+ return false;
+ }
+
+ private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first)
+ {
+ if (block.Type == AstBlockType.DoWhile && first == block.First)
+ {
+ //We only need to insert the continue if we're not at the end of the loop,
+ //or if our condition is different from the loop condition.
+ if (Next(stmt.Goto) != null || block.Condition != stmt.Condition)
+ {
+ EncloseSingleInst(stmt, Instruction.LoopContinue);
+ }
+
+ //Modify the do-while condition to allow it to continue.
+ if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition))
+ {
+ block.OrCondition(stmt.Condition);
+ }
+
+ return block;
+ }
+
+ return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto);
+ }
+
+ private static void EncloseSingleInst(GotoStatement stmt, Instruction inst)
+ {
+ AstBlock block = ParentBlock(stmt.Goto);
+
+ AstBlock newBlock = new AstBlock(AstBlockType.If, stmt.Condition);
+
+ block.AddAfter(stmt.Goto, newBlock);
+
+ newBlock.AddFirst(new AstOperation(inst));
+ }
+
+ private static AstBlock Enclose(
+ AstBlock block,
+ AstBlockType type,
+ IAstNode cond,
+ IAstNode first,
+ IAstNode last = null)
+ {
+ if (first == last)
+ {
+ return null;
+ }
+
+ if (type == AstBlockType.If)
+ {
+ cond = InverseCond(cond);
+ }
+
+ //Do a quick check, if we are enclosing a single block,
+ //and the block type/condition matches the one we're going
+ //to create, then we don't need a new block, we can just
+ //return the old one.
+ bool hasSingleNode = Next(first) == last;
+
+ if (hasSingleNode && BlockMatches(first, type, cond))
+ {
+ return first as AstBlock;
+ }
+
+ AstBlock newBlock = new AstBlock(type, cond);
+
+ block.AddBefore(first, newBlock);
+
+ while (first != last)
+ {
+ IAstNode next = Next(first);
+
+ block.Remove(first);
+
+ newBlock.Add(first);
+
+ first = next;
+ }
+
+ return newBlock;
+ }
+
+ private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond)
+ {
+ if (!(node is AstBlock block))
+ {
+ return false;
+ }
+
+ return block.Type == type && IsSameCond(block.Condition, cond);
+ }
+
+ private static bool IsSameCond(IAstNode lCond, IAstNode rCond)
+ {
+ if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot)
+ {
+ if (!(rCond is AstOperation rCondOp) || rCondOp.Inst != lCondOp.Inst)
+ {
+ return false;
+ }
+
+ lCond = lCondOp.GetSource(0);
+ rCond = rCondOp.GetSource(0);
+ }
+
+ return lCond == rCond;
+ }
+
+ private static AstBlock ParentBlock(IAstNode node)
+ {
+ if (node is AstBlock block)
+ {
+ return block.Parent;
+ }
+
+ while (!(node is AstBlock))
+ {
+ node = node.Parent;
+ }
+
+ return node as AstBlock;
+ }
+
+ private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom)
+ {
+ AstBlock block = bottom;
+
+ List<AstBlock> path = new List<AstBlock>();
+
+ while (block != top)
+ {
+ path.Add(block);
+
+ block = block.Parent;
+ }
+
+ return path.ToArray();
+ }
+
+ private static int Level(IAstNode node)
+ {
+ int level = 0;
+
+ while (node != null)
+ {
+ level++;
+
+ node = node.Parent;
+ }
+
+ return level;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs b/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs
new file mode 100644
index 00000000..25216e55
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class GotoStatement
+ {
+ public AstOperation Goto { get; }
+ public AstAssignment Label { get; }
+
+ public IAstNode Condition => Label.Destination;
+
+ public bool IsLoop { get; set; }
+
+ public bool IsUnconditional => Goto.Inst == Instruction.Branch;
+
+ public GotoStatement(AstOperation branch, AstAssignment label, bool isLoop)
+ {
+ Goto = branch;
+ Label = label;
+ IsLoop = isLoop;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs b/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs
new file mode 100644
index 00000000..5ececbb5
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ interface IAstNode
+ {
+ AstBlock Parent { get; set; }
+
+ LinkedListNode<IAstNode> LLNode { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs
new file mode 100644
index 00000000..46a61553
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs
@@ -0,0 +1,142 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class InstructionInfo
+ {
+ private struct InstInfo
+ {
+ public VariableType DestType { get; }
+
+ public VariableType[] SrcTypes { get; }
+
+ public InstInfo(VariableType destType, params VariableType[] srcTypes)
+ {
+ DestType = destType;
+ SrcTypes = srcTypes;
+ }
+ }
+
+ private static InstInfo[] _infoTbl;
+
+ static InstructionInfo()
+ {
+ _infoTbl = new InstInfo[(int)Instruction.Count];
+
+ Add(Instruction.Absolute, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Add, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.BitfieldExtractS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32);
+ Add(Instruction.BitfieldExtractU32, VariableType.U32, VariableType.U32, VariableType.S32, VariableType.S32);
+ Add(Instruction.BitfieldInsert, VariableType.Int, VariableType.Int, VariableType.Int, VariableType.S32, VariableType.S32);
+ Add(Instruction.BitfieldReverse, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseAnd, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseExclusiveOr, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseNot, VariableType.Int, VariableType.Int);
+ Add(Instruction.BitwiseOr, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.BranchIfTrue, VariableType.None, VariableType.Bool);
+ Add(Instruction.BranchIfFalse, VariableType.None, VariableType.Bool);
+ Add(Instruction.Ceiling, VariableType.F32, VariableType.F32, VariableType.F32);
+ Add(Instruction.Clamp, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ClampU32, VariableType.U32, VariableType.U32, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareGreater, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareGreaterOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareGreaterOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareGreaterU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareLess, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareLessOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.CompareLessOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareLessU32, VariableType.Bool, VariableType.U32, VariableType.U32);
+ Add(Instruction.CompareNotEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ConditionalSelect, VariableType.Scalar, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ConvertFPToS32, VariableType.S32, VariableType.F32);
+ Add(Instruction.ConvertS32ToFP, VariableType.F32, VariableType.S32);
+ Add(Instruction.ConvertU32ToFP, VariableType.F32, VariableType.U32);
+ Add(Instruction.Cosine, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Floor, VariableType.F32, VariableType.F32);
+ Add(Instruction.FusedMultiplyAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.F32);
+ Add(Instruction.IsNan, VariableType.Bool, VariableType.F32);
+ Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
+ Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.LogicalNot, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.LogicalOr, VariableType.Bool, VariableType.Bool, VariableType.Bool);
+ Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int);
+ Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int);
+ Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int);
+ Add(Instruction.Maximum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.MaximumU32, VariableType.U32, VariableType.U32, VariableType.U32);
+ Add(Instruction.Minimum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32);
+ Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32);
+ Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
+ Add(Instruction.TextureSample, VariableType.F32);
+ Add(Instruction.TextureSize, VariableType.S32, VariableType.S32, VariableType.S32);
+ Add(Instruction.Truncate, VariableType.F32, VariableType.F32);
+ Add(Instruction.UnpackHalf2x16, VariableType.F32, VariableType.U32);
+ }
+
+ private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes)
+ {
+ _infoTbl[(int)inst] = new InstInfo(destType, srcTypes);
+ }
+
+ public static VariableType GetDestVarType(Instruction inst)
+ {
+ return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst);
+ }
+
+ public static VariableType GetSrcVarType(Instruction inst, int index)
+ {
+ if (inst == Instruction.TextureSample)
+ {
+ return VariableType.F32;
+ }
+
+ return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst);
+ }
+
+ private static VariableType GetFinalVarType(VariableType type, Instruction inst)
+ {
+ if (type == VariableType.Scalar)
+ {
+ return (inst & Instruction.FP) != 0
+ ? VariableType.F32
+ : VariableType.S32;
+ }
+ else if (type == VariableType.Int)
+ {
+ return VariableType.S32;
+ }
+ else if (type == VariableType.None)
+ {
+ throw new ArgumentException($"Invalid operand for instruction \"{inst}\".");
+ }
+
+ return type;
+ }
+
+ public static bool IsUnary(Instruction inst)
+ {
+ if (inst == Instruction.Copy)
+ {
+ return true;
+ }
+ else if (inst == Instruction.TextureSample)
+ {
+ return false;
+ }
+
+ return _infoTbl[(int)(inst & Instruction.Mask)].SrcTypes.Length == 1;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs
new file mode 100644
index 00000000..a3a8d138
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs
@@ -0,0 +1,34 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class OperandInfo
+ {
+ public static VariableType GetVarType(AstOperand operand)
+ {
+ if (operand.Type == OperandType.LocalVariable)
+ {
+ return operand.VarType;
+ }
+ else
+ {
+ return GetVarType(operand.Type);
+ }
+ }
+
+ public static VariableType GetVarType(OperandType type)
+ {
+ switch (type)
+ {
+ case OperandType.Attribute: return VariableType.F32;
+ case OperandType.Constant: return VariableType.S32;
+ case OperandType.ConstantBuffer: return VariableType.F32;
+ case OperandType.GlobalMemory: return VariableType.F32;
+ case OperandType.Undefined: return VariableType.S32;
+ }
+
+ throw new ArgumentException($"Invalid operand type \"{type}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs b/Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs
new file mode 100644
index 00000000..53391b62
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs
@@ -0,0 +1,74 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class PhiFunctions
+ {
+ public static void Remove(BasicBlock[] blocks)
+ {
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ LinkedListNode<INode> node = block.Operations.First;
+
+ while (node != null)
+ {
+ LinkedListNode<INode> nextNode = node.Next;
+
+ if (!(node.Value is PhiNode phi))
+ {
+ node = nextNode;
+
+ continue;
+ }
+
+ for (int index = 0; index < phi.SourcesCount; index++)
+ {
+ Operand src = phi.GetSource(index);
+
+ BasicBlock srcBlock = phi.GetBlock(index);
+
+ Operation copyOp = new Operation(Instruction.Copy, phi.Dest, src);
+
+ AddBeforeBranch(srcBlock, copyOp);
+ }
+
+ block.Operations.Remove(node);
+
+ node = nextNode;
+ }
+ }
+ }
+
+ private static void AddBeforeBranch(BasicBlock block, INode node)
+ {
+ INode lastOp = block.GetLastOp();
+
+ if (lastOp is Operation operation && IsControlFlowInst(operation.Inst))
+ {
+ block.Operations.AddBefore(block.Operations.Last, node);
+ }
+ else
+ {
+ block.Operations.AddLast(node);
+ }
+ }
+
+ private static bool IsControlFlowInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.Branch:
+ case Instruction.BranchIfFalse:
+ case Instruction.BranchIfTrue:
+ case Instruction.Discard:
+ case Instruction.Return:
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs
new file mode 100644
index 00000000..f65631be
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs
@@ -0,0 +1,254 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ static class StructuredProgram
+ {
+ public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks)
+ {
+ PhiFunctions.Remove(blocks);
+
+ StructuredProgramContext context = new StructuredProgramContext(blocks.Length);
+
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ context.EnterBlock(block);
+
+ foreach (INode node in block.Operations)
+ {
+ Operation operation = (Operation)node;
+
+ if (IsBranchInst(operation.Inst))
+ {
+ context.LeaveBlock(block, operation);
+ }
+ else
+ {
+ AddOperation(context, operation);
+ }
+ }
+ }
+
+ GotoElimination.Eliminate(context.GetGotos());
+
+ AstOptimizer.Optimize(context.Info);
+
+ return context.Info;
+ }
+
+ private static void AddOperation(StructuredProgramContext context, Operation operation)
+ {
+ Instruction inst = operation.Inst;
+
+ IAstNode[] sources = new IAstNode[operation.SourcesCount];
+
+ for (int index = 0; index < sources.Length; index++)
+ {
+ sources[index] = context.GetOperandUse(operation.GetSource(index));
+ }
+
+ if (operation.Dest != null)
+ {
+ AstOperand dest = context.GetOperandDef(operation.Dest);
+
+ if (inst == Instruction.LoadConstant)
+ {
+ Operand ldcSource = operation.GetSource(0);
+
+ if (ldcSource.Type != OperandType.Constant)
+ {
+ throw new InvalidOperationException("Found LDC with non-constant constant buffer slot.");
+ }
+
+ context.Info.CBuffers.Add(ldcSource.Value);
+ }
+
+ AstAssignment assignment;
+
+ //If all the sources are bool, it's better to use short-circuiting
+ //logical operations, rather than forcing a cast to int and doing
+ //a bitwise operation with the value, as it is likely to be used as
+ //a bool in the end.
+ if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, VariableType.Bool))
+ {
+ inst = GetLogicalFromBitwiseInst(inst);
+ }
+
+ bool isCondSel = inst == Instruction.ConditionalSelect;
+ bool isCopy = inst == Instruction.Copy;
+
+ if (isCondSel || isCopy)
+ {
+ VariableType type = GetVarTypeFromUses(operation.Dest);
+
+ if (isCondSel && type == VariableType.F32)
+ {
+ inst |= Instruction.FP;
+ }
+
+ dest.VarType = type;
+ }
+ else
+ {
+ dest.VarType = InstructionInfo.GetDestVarType(inst);
+ }
+
+ int componentMask = 1 << operation.ComponentIndex;
+
+ IAstNode source;
+
+ if (operation is TextureOperation texOp)
+ {
+ AstTextureOperation astTexOp = new AstTextureOperation(
+ inst,
+ texOp.Type,
+ texOp.Flags,
+ texOp.Handle,
+ componentMask,
+ sources);
+
+ context.Info.Samplers.Add(astTexOp);
+
+ source = astTexOp;
+ }
+ else if (!isCopy)
+ {
+ source = new AstOperation(inst, componentMask, sources);
+ }
+ else
+ {
+ source = sources[0];
+ }
+
+ assignment = new AstAssignment(dest, source);
+
+ context.AddNode(assignment);
+ }
+ else
+ {
+ context.AddNode(new AstOperation(inst, sources));
+ }
+ }
+
+ private static VariableType GetVarTypeFromUses(Operand dest)
+ {
+ HashSet<Operand> visited = new HashSet<Operand>();
+
+ Queue<Operand> pending = new Queue<Operand>();
+
+ bool Enqueue(Operand operand)
+ {
+ if (visited.Add(operand))
+ {
+ pending.Enqueue(operand);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ Enqueue(dest);
+
+ while (pending.TryDequeue(out Operand operand))
+ {
+ foreach (INode useNode in operand.UseOps)
+ {
+ if (!(useNode is Operation operation))
+ {
+ continue;
+ }
+
+ if (operation.Inst == Instruction.Copy)
+ {
+ if (operation.Dest.Type == OperandType.LocalVariable)
+ {
+ if (Enqueue(operation.Dest))
+ {
+ break;
+ }
+ }
+ else
+ {
+ return OperandInfo.GetVarType(operation.Dest.Type);
+ }
+ }
+ else
+ {
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ if (operation.GetSource(index) == operand)
+ {
+ return InstructionInfo.GetSrcVarType(operation.Inst, index);
+ }
+ }
+ }
+ }
+ }
+
+ return VariableType.S32;
+ }
+
+ private static bool AreAllSourceTypesEqual(IAstNode[] sources, VariableType type)
+ {
+ foreach (IAstNode node in sources)
+ {
+ if (!(node is AstOperand operand))
+ {
+ return false;
+ }
+
+ if (operand.VarType != type)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool IsBranchInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.Branch:
+ case Instruction.BranchIfFalse:
+ case Instruction.BranchIfTrue:
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsBitwiseInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.BitwiseAnd:
+ case Instruction.BitwiseExclusiveOr:
+ case Instruction.BitwiseNot:
+ case Instruction.BitwiseOr:
+ return true;
+ }
+
+ return false;
+ }
+
+ private static Instruction GetLogicalFromBitwiseInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.BitwiseAnd: return Instruction.LogicalAnd;
+ case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr;
+ case Instruction.BitwiseNot: return Instruction.LogicalNot;
+ case Instruction.BitwiseOr: return Instruction.LogicalOr;
+ }
+
+ throw new ArgumentException($"Unexpected instruction \"{inst}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs
new file mode 100644
index 00000000..e1f0503a
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs
@@ -0,0 +1,292 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+using System.Linq;
+
+using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class StructuredProgramContext
+ {
+ private HashSet<BasicBlock> _loopTails;
+
+ private Stack<(AstBlock Block, int EndIndex)> _blockStack;
+
+ private Dictionary<Operand, AstOperand> _localsMap;
+
+ private Dictionary<int, AstAssignment> _gotoTempAsgs;
+
+ private List<GotoStatement> _gotos;
+
+ private AstBlock _currBlock;
+
+ private int _currEndIndex;
+
+ public StructuredProgramInfo Info { get; }
+
+ public StructuredProgramContext(int blocksCount)
+ {
+ _loopTails = new HashSet<BasicBlock>();
+
+ _blockStack = new Stack<(AstBlock, int)>();
+
+ _localsMap = new Dictionary<Operand, AstOperand>();
+
+ _gotoTempAsgs = new Dictionary<int, AstAssignment>();
+
+ _gotos = new List<GotoStatement>();
+
+ _currBlock = new AstBlock(AstBlockType.Main);
+
+ _currEndIndex = blocksCount;
+
+ Info = new StructuredProgramInfo(_currBlock);
+ }
+
+ public void EnterBlock(BasicBlock block)
+ {
+ while (_currEndIndex == block.Index)
+ {
+ (_currBlock, _currEndIndex) = _blockStack.Pop();
+ }
+
+ if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg))
+ {
+ AddGotoTempReset(block, gotoTempAsg);
+ }
+
+ LookForDoWhileStatements(block);
+ }
+
+ public void LeaveBlock(BasicBlock block, Operation branchOp)
+ {
+ LookForIfStatements(block, branchOp);
+ }
+
+ private void LookForDoWhileStatements(BasicBlock block)
+ {
+ //Check if we have any predecessor whose index is greater than the
+ //current block, this indicates a loop.
+ bool done = false;
+
+ foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index))
+ {
+ if (predecessor.Index < block.Index)
+ {
+ break;
+ }
+
+ if (predecessor.Index < _currEndIndex && !done)
+ {
+ Operation branchOp = (Operation)predecessor.GetLastOp();
+
+ NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1);
+
+ _loopTails.Add(predecessor);
+
+ done = true;
+ }
+ else
+ {
+ AddGotoTempReset(block, GetGotoTempAsg(block.Index));
+
+ break;
+ }
+ }
+ }
+
+ private void LookForIfStatements(BasicBlock block, Operation branchOp)
+ {
+ if (block.Branch == null)
+ {
+ return;
+ }
+
+ bool isLoop = block.Branch.Index <= block.Index;
+
+ if (block.Branch.Index <= _currEndIndex && !isLoop)
+ {
+ NewBlock(AstBlockType.If, branchOp, block.Branch.Index);
+ }
+ else if (!_loopTails.Contains(block))
+ {
+ AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index);
+
+ IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp);
+
+ AddNode(Assign(gotoTempAsg.Destination, cond));
+
+ AstOperation branch = new AstOperation(branchOp.Inst);
+
+ AddNode(branch);
+
+ GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop);
+
+ _gotos.Add(gotoStmt);
+ }
+ }
+
+ private AstAssignment GetGotoTempAsg(int index)
+ {
+ if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg))
+ {
+ return gotoTempAsg;
+ }
+
+ AstOperand gotoTemp = NewTemp(VariableType.Bool);
+
+ gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False));
+
+ _gotoTempAsgs.Add(index, gotoTempAsg);
+
+ return gotoTempAsg;
+ }
+
+ private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg)
+ {
+ AddNode(gotoTempAsg);
+
+ //For block 0, we don't need to add the extra "reset" at the beggining,
+ //because it is already the first node to be executed on the shader,
+ //so it is reset to false by the "local" assignment anyway.
+ if (block.Index != 0)
+ {
+ Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False)));
+ }
+ }
+
+ private void NewBlock(AstBlockType type, Operation branchOp, int endIndex)
+ {
+ NewBlock(type, GetBranchCond(type, branchOp), endIndex);
+ }
+
+ private void NewBlock(AstBlockType type, IAstNode cond, int endIndex)
+ {
+ AstBlock childBlock = new AstBlock(type, cond);
+
+ AddNode(childBlock);
+
+ _blockStack.Push((_currBlock, _currEndIndex));
+
+ _currBlock = childBlock;
+ _currEndIndex = endIndex;
+ }
+
+ private IAstNode GetBranchCond(AstBlockType type, Operation branchOp)
+ {
+ IAstNode cond;
+
+ if (branchOp.Inst == Instruction.Branch)
+ {
+ cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True);
+ }
+ else
+ {
+ cond = GetOperandUse(branchOp.GetSource(0));
+
+ Instruction invInst = type == AstBlockType.If
+ ? Instruction.BranchIfTrue
+ : Instruction.BranchIfFalse;
+
+ if (branchOp.Inst == invInst)
+ {
+ cond = new AstOperation(Instruction.LogicalNot, cond);
+ }
+ }
+
+ return cond;
+ }
+
+ public void AddNode(IAstNode node)
+ {
+ _currBlock.Add(node);
+ }
+
+ public GotoStatement[] GetGotos()
+ {
+ return _gotos.ToArray();
+ }
+
+ private AstOperand NewTemp(VariableType type)
+ {
+ AstOperand newTemp = Local(type);
+
+ Info.Locals.Add(newTemp);
+
+ return newTemp;
+ }
+
+ public AstOperand GetOperandDef(Operand operand)
+ {
+ if (TryGetUserAttributeIndex(operand, out int attrIndex))
+ {
+ Info.OAttributes.Add(attrIndex);
+ }
+
+ return GetOperand(operand);
+ }
+
+ public AstOperand GetOperandUse(Operand operand)
+ {
+ if (TryGetUserAttributeIndex(operand, out int attrIndex))
+ {
+ Info.IAttributes.Add(attrIndex);
+ }
+ else if (operand.Type == OperandType.ConstantBuffer)
+ {
+ Info.CBuffers.Add(operand.GetCbufSlot());
+ }
+
+ return GetOperand(operand);
+ }
+
+ private AstOperand GetOperand(Operand operand)
+ {
+ if (operand == null)
+ {
+ return null;
+ }
+
+ if (operand.Type != OperandType.LocalVariable)
+ {
+ return new AstOperand(operand);
+ }
+
+ if (!_localsMap.TryGetValue(operand, out AstOperand astOperand))
+ {
+ astOperand = new AstOperand(operand);
+
+ _localsMap.Add(operand, astOperand);
+
+ Info.Locals.Add(astOperand);
+ }
+
+ return astOperand;
+ }
+
+ private static bool TryGetUserAttributeIndex(Operand operand, out int attrIndex)
+ {
+ if (operand.Type == OperandType.Attribute)
+ {
+ if (operand.Value >= AttributeConsts.UserAttributeBase &&
+ operand.Value < AttributeConsts.UserAttributeEnd)
+ {
+ attrIndex = (operand.Value - AttributeConsts.UserAttributeBase) >> 4;
+
+ return true;
+ }
+ else if (operand.Value >= AttributeConsts.FragmentOutputColorBase &&
+ operand.Value < AttributeConsts.FragmentOutputColorEnd)
+ {
+ attrIndex = (operand.Value - AttributeConsts.FragmentOutputColorBase) >> 4;
+
+ return true;
+ }
+ }
+
+ attrIndex = 0;
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs
new file mode 100644
index 00000000..d368ef00
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ class StructuredProgramInfo
+ {
+ public AstBlock MainBlock { get; }
+
+ public HashSet<AstOperand> Locals { get; }
+
+ public HashSet<int> CBuffers { get; }
+
+ public HashSet<int> IAttributes { get; }
+ public HashSet<int> OAttributes { get; }
+
+ public HashSet<AstTextureOperation> Samplers { get; }
+
+ public StructuredProgramInfo(AstBlock mainBlock)
+ {
+ MainBlock = mainBlock;
+
+ Locals = new HashSet<AstOperand>();
+
+ CBuffers = new HashSet<int>();
+
+ IAttributes = new HashSet<int>();
+ OAttributes = new HashSet<int>();
+
+ Samplers = new HashSet<AstTextureOperation>();
+ }
+ }
+} \ 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 00000000..4c7f3849
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Shader.StructuredIr
+{
+ enum VariableType
+ {
+ None,
+ Bool,
+ Scalar,
+ Int,
+ F32,
+ S32,
+ U32
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/TextureDescriptor.cs b/Ryujinx.Graphics/Shader/TextureDescriptor.cs
new file mode 100644
index 00000000..96f0f5b1
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/TextureDescriptor.cs
@@ -0,0 +1,36 @@
+namespace Ryujinx.Graphics.Shader
+{
+ public struct TextureDescriptor
+ {
+ public string Name { get; }
+
+ public int HandleIndex { get; }
+
+ public bool IsBindless { get; }
+
+ public int CbufSlot { get; }
+ public int CbufOffset { get; }
+
+ public TextureDescriptor(string name, int hIndex)
+ {
+ Name = name;
+ HandleIndex = hIndex;
+
+ IsBindless = false;
+
+ CbufSlot = 0;
+ CbufOffset = 0;
+ }
+
+ public TextureDescriptor(string name, int cbufSlot, int cbufOffset)
+ {
+ Name = name;
+ HandleIndex = 0;
+
+ IsBindless = true;
+
+ CbufSlot = cbufSlot;
+ CbufOffset = cbufOffset;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs
new file mode 100644
index 00000000..ae3e361c
--- /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 00000000..e2ca74a4
--- /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<Operand, BasicBlock> labels = new Dictionary<Operand, BasicBlock>();
+
+ List<BasicBlock> blocks = new List<BasicBlock>();
+
+ 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 00000000..b4b80e3e
--- /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<BasicBlock> visited = new HashSet<BasicBlock>();
+
+ Stack<BasicBlock> blockStack = new Stack<BasicBlock>();
+
+ List<BasicBlock> postOrderBlocks = new List<BasicBlock>(blocksCount);
+
+ int[] postOrderMap = new int[blocksCount];
+
+ visited.Add(entry);
+
+ blockStack.Push(entry);
+
+ while (blockStack.TryPop(out BasicBlock block))
+ {
+ if (block.Next != null && visited.Add(block.Next))
+ {
+ blockStack.Push(block);
+ blockStack.Push(block.Next);
+ }
+ else if (block.Branch != null && visited.Add(block.Branch))
+ {
+ blockStack.Push(block);
+ blockStack.Push(block.Branch);
+ }
+ else
+ {
+ postOrderMap[block.Index] = postOrderBlocks.Count;
+
+ postOrderBlocks.Add(block);
+ }
+ }
+
+ BasicBlock Intersect(BasicBlock block1, BasicBlock block2)
+ {
+ while (block1 != block2)
+ {
+ while (postOrderMap[block1.Index] < postOrderMap[block2.Index])
+ {
+ block1 = block1.ImmediateDominator;
+ }
+
+ while (postOrderMap[block2.Index] < postOrderMap[block1.Index])
+ {
+ block2 = block2.ImmediateDominator;
+ }
+ }
+
+ return block1;
+ }
+
+ entry.ImmediateDominator = entry;
+
+ bool modified;
+
+ do
+ {
+ modified = false;
+
+ for (int blkIndex = postOrderBlocks.Count - 2; blkIndex >= 0; blkIndex--)
+ {
+ BasicBlock block = postOrderBlocks[blkIndex];
+
+ BasicBlock newIDom = null;
+
+ foreach (BasicBlock predecessor in block.Predecessors)
+ {
+ if (predecessor.ImmediateDominator != null)
+ {
+ if (newIDom != null)
+ {
+ newIDom = Intersect(predecessor, newIDom);
+ }
+ else
+ {
+ newIDom = predecessor;
+ }
+ }
+ }
+
+ if (block.ImmediateDominator != newIDom)
+ {
+ block.ImmediateDominator = newIDom;
+
+ modified = true;
+ }
+ }
+ }
+ while (modified);
+ }
+
+ public static void FindDominanceFrontiers(BasicBlock[] blocks)
+ {
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ if (block.Predecessors.Count < 2)
+ {
+ continue;
+ }
+
+ for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++)
+ {
+ BasicBlock current = block.Predecessors[pBlkIndex];
+
+ while (current != block.ImmediateDominator)
+ {
+ current.DominanceFrontiers.Add(block);
+
+ current = current.ImmediateDominator;
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs
new file mode 100644
index 00000000..6c2bf6e4
--- /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<Operation> _operations;
+
+ private Dictionary<ulong, Operand> _labels;
+
+ public EmitterContext(GalShaderType shaderType, ShaderHeader header)
+ {
+ _shaderType = shaderType;
+ _header = header;
+
+ _operations = new List<Operation>();
+
+ _labels = new Dictionary<ulong, Operand>();
+ }
+
+ public Operand Add(Instruction inst, Operand dest = null, params Operand[] sources)
+ {
+ Operation operation = new Operation(inst, dest, sources);
+
+ Add(operation);
+
+ return dest;
+ }
+
+ public void Add(Operation operation)
+ {
+ _operations.Add(operation);
+ }
+
+ public void MarkLabel(Operand label)
+ {
+ Add(Instruction.MarkLabel, label);
+ }
+
+ public Operand GetLabel(ulong address)
+ {
+ if (!_labels.TryGetValue(address, out Operand label))
+ {
+ label = Label();
+
+ _labels.Add(address, label);
+ }
+
+ return label;
+ }
+
+ public void PrepareForReturn()
+ {
+ if (_shaderType == GalShaderType.Fragment)
+ {
+ if (_header.OmapDepth)
+ {
+ Operand dest = Attribute(AttributeConsts.FragmentOutputDepth);
+
+ Operand src = Register(_header.DepthRegister, RegisterType.Gpr);
+
+ this.Copy(dest, src);
+ }
+
+ int regIndex = 0;
+
+ for (int attachment = 0; attachment < 8; attachment++)
+ {
+ OutputMapTarget target = _header.OmapTargets[attachment];
+
+ for (int component = 0; component < 4; component++)
+ {
+ if (target.ComponentEnabled(component))
+ {
+ Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + regIndex * 4);
+
+ Operand src = Register(regIndex, RegisterType.Gpr);
+
+ this.Copy(dest, src);
+
+ regIndex++;
+ }
+ }
+ }
+ }
+ }
+
+ public Operation[] GetOperations()
+ {
+ return _operations.ToArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs
new file mode 100644
index 00000000..604aa67d
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs
@@ -0,0 +1,420 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ static class EmitterContextInsts
+ {
+ public static Operand BitfieldExtractS32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.BitfieldExtractS32, Local(), a, b, c);
+ }
+
+ public static Operand BitfieldExtractU32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.BitfieldExtractU32, Local(), a, b, c);
+ }
+
+ public static Operand BitfieldInsert(this EmitterContext context, Operand a, Operand b, Operand c, Operand d)
+ {
+ return context.Add(Instruction.BitfieldInsert, Local(), a, b, c, d);
+ }
+
+ public static Operand BitfieldReverse(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.BitfieldReverse, Local(), a);
+ }
+
+ public static Operand BitwiseAnd(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.BitwiseAnd, Local(), a, b);
+ }
+
+ public static Operand BitwiseExclusiveOr(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.BitwiseExclusiveOr, Local(), a, b);
+ }
+
+ public static Operand BitwiseNot(this EmitterContext context, Operand a, bool invert)
+ {
+ if (invert)
+ {
+ a = context.BitwiseNot(a);
+ }
+
+ return a;
+ }
+
+ public static Operand BitwiseNot(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.BitwiseNot, Local(), a);
+ }
+
+ public static Operand BitwiseOr(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.BitwiseOr, Local(), a, b);
+ }
+
+ public static Operand Branch(this EmitterContext context, Operand d)
+ {
+ return context.Add(Instruction.Branch, d);
+ }
+
+ public static Operand BranchIfFalse(this EmitterContext context, Operand d, Operand a)
+ {
+ return context.Add(Instruction.BranchIfFalse, d, a);
+ }
+
+ public static Operand BranchIfTrue(this EmitterContext context, Operand d, Operand a)
+ {
+ return context.Add(Instruction.BranchIfTrue, d, a);
+ }
+
+ public static Operand ConditionalSelect(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.ConditionalSelect, Local(), a, b, c);
+ }
+
+ public static Operand Copy(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Copy, Local(), a);
+ }
+
+ public static void Copy(this EmitterContext context, Operand d, Operand a)
+ {
+ if (d.Type == OperandType.Constant)
+ {
+ return;
+ }
+
+ context.Add(Instruction.Copy, d, a);
+ }
+
+ public static Operand Discard(this EmitterContext context)
+ {
+ return context.Add(Instruction.Discard);
+ }
+
+ public static Operand EmitVertex(this EmitterContext context)
+ {
+ return context.Add(Instruction.EmitVertex);
+ }
+
+ public static Operand EndPrimitive(this EmitterContext context)
+ {
+ return context.Add(Instruction.EndPrimitive);
+ }
+
+ public static Operand FPAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg)
+ {
+ return context.FPNegate(context.FPAbsolute(a, abs), neg);
+ }
+
+ public static Operand FPAbsolute(this EmitterContext context, Operand a, bool abs)
+ {
+ if (abs)
+ {
+ a = context.FPAbsolute(a);
+ }
+
+ return a;
+ }
+
+ public static Operand FPAbsolute(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Absolute, Local(), a);
+ }
+
+ public static Operand FPAdd(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Add, Local(), a, b);
+ }
+
+ public static Operand FPCeiling(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Ceiling, Local(), a);
+ }
+
+ public static Operand FPCompareEqual(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.CompareEqual, Local(), a, b);
+ }
+
+ public static Operand FPCompareLess(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.CompareLess, Local(), a, b);
+ }
+
+ public static Operand FPConvertToS32(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.ConvertFPToS32, Local(), a);
+ }
+
+ public static Operand FPCosine(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Cosine, Local(), a);
+ }
+
+ public static Operand FPDivide(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Divide, Local(), a, b);
+ }
+
+ public static Operand FPExponentB2(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.ExponentB2, Local(), a);
+ }
+
+ public static Operand FPFloor(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Floor, Local(), a);
+ }
+
+ public static Operand FPLogarithmB2(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.LogarithmB2, Local(), a);
+ }
+
+ public static Operand FPMaximum(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Maximum, Local(), a, b);
+ }
+
+ public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Minimum, Local(), a, b);
+ }
+
+ public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Multiply, Local(), a, b);
+ }
+
+ public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.FusedMultiplyAdd, Local(), a, b, c);
+ }
+
+ public static Operand FPNegate(this EmitterContext context, Operand a, bool neg)
+ {
+ if (neg)
+ {
+ a = context.FPNegate(a);
+ }
+
+ return a;
+ }
+
+ public static Operand FPNegate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Negate, Local(), a);
+ }
+
+ public static Operand FPReciprocal(this EmitterContext context, Operand a)
+ {
+ return context.FPDivide(ConstF(1), a);
+ }
+
+ public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.ReciprocalSquareRoot, Local(), a);
+ }
+
+ public static Operand FPSaturate(this EmitterContext context, Operand a, bool sat)
+ {
+ if (sat)
+ {
+ a = context.FPSaturate(a);
+ }
+
+ return a;
+ }
+
+ public static Operand FPSaturate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1));
+ }
+
+ public static Operand FPSine(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Sine, Local(), a);
+ }
+
+ public static Operand FPSquareRoot(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.SquareRoot, Local(), a);
+ }
+
+ public static Operand FPTruncate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Truncate, Local(), a);
+ }
+
+ public static Operand IAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg)
+ {
+ return context.INegate(context.IAbsolute(a, abs), neg);
+ }
+
+ public static Operand IAbsolute(this EmitterContext context, Operand a, bool abs)
+ {
+ if (abs)
+ {
+ a = context.IAbsolute(a);
+ }
+
+ return a;
+ }
+
+ public static Operand IAbsolute(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Absolute, Local(), a);
+ }
+
+ public static Operand IAdd(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Add, Local(), a, b);
+ }
+
+ public static Operand IClampS32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.Clamp, Local(), a, b, c);
+ }
+
+ public static Operand IClampU32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.ClampU32, Local(), a, b, c);
+ }
+
+ public static Operand ICompareEqual(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareEqual, Local(), a, b);
+ }
+
+ public static Operand ICompareLess(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareLess, Local(), a, b);
+ }
+
+ public static Operand ICompareLessUnsigned(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareLessU32, Local(), a, b);
+ }
+
+ public static Operand ICompareNotEqual(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareNotEqual, Local(), a, b);
+ }
+
+ public static Operand IConvertS32ToFP(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.ConvertS32ToFP, Local(), a);
+ }
+
+ public static Operand IConvertU32ToFP(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.ConvertU32ToFP, Local(), a);
+ }
+
+ public static Operand IMaximumS32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Maximum, Local(), a, b);
+ }
+
+ public static Operand IMaximumU32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.MaximumU32, Local(), a, b);
+ }
+
+ public static Operand IMinimumS32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Minimum, Local(), a, b);
+ }
+
+ public static Operand IMinimumU32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.MinimumU32, Local(), a, b);
+ }
+
+ public static Operand IMultiply(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Multiply, Local(), a, b);
+ }
+
+ public static Operand INegate(this EmitterContext context, Operand a, bool neg)
+ {
+ if (neg)
+ {
+ a = context.INegate(a);
+ }
+
+ return a;
+ }
+
+ public static Operand INegate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Negate, Local(), a);
+ }
+
+ public static Operand ISubtract(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Subtract, Local(), a, b);
+ }
+
+ public static Operand IsNan(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.IsNan, Local(), a);
+ }
+
+ public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.LoadConstant, Local(), a, b);
+ }
+
+ public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.PackHalf2x16, Local(), a, b);
+ }
+
+ public static Operand Return(this EmitterContext context)
+ {
+ context.PrepareForReturn();
+
+ return context.Add(Instruction.Return);
+ }
+
+ public static Operand ShiftLeft(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.ShiftLeft, Local(), a, b);
+ }
+
+ public static Operand ShiftRightS32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.ShiftRightS32, Local(), a, b);
+ }
+
+ public static Operand ShiftRightU32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.ShiftRightU32, Local(), a, b);
+ }
+
+ public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a)
+ {
+ return UnpackHalf2x16(context, a, 1);
+ }
+
+ public static Operand UnpackHalf2x16Low(this EmitterContext context, Operand a)
+ {
+ return UnpackHalf2x16(context, a, 0);
+ }
+
+ private static Operand UnpackHalf2x16(this EmitterContext context, Operand a, int index)
+ {
+ Operand dest = Local();
+
+ context.Add(new Operation(Instruction.UnpackHalf2x16, index, dest, a));
+
+ return dest;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs
new file mode 100644
index 00000000..2b0f1905
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs
@@ -0,0 +1,64 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class BranchElimination
+ {
+ public static bool Eliminate(BasicBlock block)
+ {
+ if (block.HasBranch && IsRedundantBranch((Operation)block.GetLastOp(), Next(block)))
+ {
+ block.Branch = null;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsRedundantBranch(Operation current, BasicBlock nextBlock)
+ {
+ //Here we check that:
+ //- The current block ends with a branch.
+ //- The next block only contains a branch.
+ //- The branch on the next block is unconditional.
+ //- Both branches are jumping to the same location.
+ //In this case, the branch on the current block can be removed,
+ //as the next block is going to jump to the same place anyway.
+ if (nextBlock == null)
+ {
+ return false;
+ }
+
+ if (!(nextBlock.Operations.First?.Value is Operation next))
+ {
+ return false;
+ }
+
+ if (next.Inst != Instruction.Branch)
+ {
+ return false;
+ }
+
+ return current.Dest == next.Dest;
+ }
+
+ private static BasicBlock Next(BasicBlock block)
+ {
+ block = block.Next;
+
+ while (block != null && block.Operations.Count == 0)
+ {
+ if (block.HasBranch)
+ {
+ throw new InvalidOperationException("Found a bogus empty block that \"ends with a branch\".");
+ }
+
+ block = block.Next;
+ }
+
+ return block;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs
new file mode 100644
index 00000000..a2e05ef1
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs
@@ -0,0 +1,323 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class ConstantFolding
+ {
+ public static void Fold(Operation operation)
+ {
+ if (!AreAllSourcesConstant(operation))
+ {
+ return;
+ }
+
+ switch (operation.Inst)
+ {
+ case Instruction.Add:
+ EvaluateBinary(operation, (x, y) => x + y);
+ break;
+
+ case Instruction.BitwiseAnd:
+ EvaluateBinary(operation, (x, y) => x & y);
+ break;
+
+ case Instruction.BitwiseExclusiveOr:
+ EvaluateBinary(operation, (x, y) => x ^ y);
+ break;
+
+ case Instruction.BitwiseNot:
+ EvaluateUnary(operation, (x) => ~x);
+ break;
+
+ case Instruction.BitwiseOr:
+ EvaluateBinary(operation, (x, y) => x | y);
+ break;
+
+ case Instruction.BitfieldExtractS32:
+ BitfieldExtractS32(operation);
+ break;
+
+ case Instruction.BitfieldExtractU32:
+ BitfieldExtractU32(operation);
+ break;
+
+ case Instruction.Clamp:
+ EvaluateTernary(operation, (x, y, z) => Math.Clamp(x, y, z));
+ break;
+
+ case Instruction.ClampU32:
+ EvaluateTernary(operation, (x, y, z) => (int)Math.Clamp((uint)x, (uint)y, (uint)z));
+ break;
+
+ case Instruction.CompareEqual:
+ EvaluateBinary(operation, (x, y) => x == y);
+ break;
+
+ case Instruction.CompareGreater:
+ EvaluateBinary(operation, (x, y) => x > y);
+ break;
+
+ case Instruction.CompareGreaterOrEqual:
+ EvaluateBinary(operation, (x, y) => x >= y);
+ break;
+
+ case Instruction.CompareGreaterOrEqualU32:
+ EvaluateBinary(operation, (x, y) => (uint)x >= (uint)y);
+ break;
+
+ case Instruction.CompareGreaterU32:
+ EvaluateBinary(operation, (x, y) => (uint)x > (uint)y);
+ break;
+
+ case Instruction.CompareLess:
+ EvaluateBinary(operation, (x, y) => x < y);
+ break;
+
+ case Instruction.CompareLessOrEqual:
+ EvaluateBinary(operation, (x, y) => x <= y);
+ break;
+
+ case Instruction.CompareLessOrEqualU32:
+ EvaluateBinary(operation, (x, y) => (uint)x <= (uint)y);
+ break;
+
+ case Instruction.CompareLessU32:
+ EvaluateBinary(operation, (x, y) => (uint)x < (uint)y);
+ break;
+
+ case Instruction.CompareNotEqual:
+ EvaluateBinary(operation, (x, y) => x != y);
+ break;
+
+ case Instruction.Divide:
+ EvaluateBinary(operation, (x, y) => y != 0 ? x / y : 0);
+ break;
+
+ case Instruction.FP | Instruction.Add:
+ EvaluateFPBinary(operation, (x, y) => x + y);
+ break;
+
+ case Instruction.FP | Instruction.Clamp:
+ EvaluateFPTernary(operation, (x, y, z) => Math.Clamp(x, y, z));
+ break;
+
+ case Instruction.FP | Instruction.CompareEqual:
+ EvaluateFPBinary(operation, (x, y) => x == y);
+ break;
+
+ case Instruction.FP | Instruction.CompareGreater:
+ EvaluateFPBinary(operation, (x, y) => x > y);
+ break;
+
+ case Instruction.FP | Instruction.CompareGreaterOrEqual:
+ EvaluateFPBinary(operation, (x, y) => x >= y);
+ break;
+
+ case Instruction.FP | Instruction.CompareLess:
+ EvaluateFPBinary(operation, (x, y) => x < y);
+ break;
+
+ case Instruction.FP | Instruction.CompareLessOrEqual:
+ EvaluateFPBinary(operation, (x, y) => x <= y);
+ break;
+
+ case Instruction.FP | Instruction.CompareNotEqual:
+ EvaluateFPBinary(operation, (x, y) => x != y);
+ break;
+
+ case Instruction.FP | Instruction.Divide:
+ EvaluateFPBinary(operation, (x, y) => x / y);
+ break;
+
+ case Instruction.FP | Instruction.Multiply:
+ EvaluateFPBinary(operation, (x, y) => x * y);
+ break;
+
+ case Instruction.FP | Instruction.Negate:
+ EvaluateFPUnary(operation, (x) => -x);
+ break;
+
+ case Instruction.FP | Instruction.Subtract:
+ EvaluateFPBinary(operation, (x, y) => x - y);
+ break;
+
+ case Instruction.IsNan:
+ EvaluateFPUnary(operation, (x) => float.IsNaN(x));
+ break;
+
+ case Instruction.Maximum:
+ EvaluateBinary(operation, (x, y) => Math.Max(x, y));
+ break;
+
+ case Instruction.MaximumU32:
+ EvaluateBinary(operation, (x, y) => (int)Math.Max((uint)x, (uint)y));
+ break;
+
+ case Instruction.Minimum:
+ EvaluateBinary(operation, (x, y) => Math.Min(x, y));
+ break;
+
+ case Instruction.MinimumU32:
+ EvaluateBinary(operation, (x, y) => (int)Math.Min((uint)x, (uint)y));
+ break;
+
+ case Instruction.Multiply:
+ EvaluateBinary(operation, (x, y) => x * y);
+ break;
+
+ case Instruction.Negate:
+ EvaluateUnary(operation, (x) => -x);
+ break;
+
+ case Instruction.ShiftLeft:
+ EvaluateBinary(operation, (x, y) => x << y);
+ break;
+
+ case Instruction.ShiftRightS32:
+ EvaluateBinary(operation, (x, y) => x >> y);
+ break;
+
+ case Instruction.ShiftRightU32:
+ EvaluateBinary(operation, (x, y) => (int)((uint)x >> y));
+ break;
+
+ case Instruction.Subtract:
+ EvaluateBinary(operation, (x, y) => x - y);
+ break;
+
+ case Instruction.UnpackHalf2x16:
+ UnpackHalf2x16(operation);
+ break;
+ }
+ }
+
+ private static bool AreAllSourcesConstant(Operation operation)
+ {
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ if (operation.GetSource(index).Type != OperandType.Constant)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static void BitfieldExtractS32(Operation operation)
+ {
+ int value = GetBitfieldExtractValue(operation);
+
+ int shift = 32 - operation.GetSource(2).Value;
+
+ value = (value << shift) >> shift;
+
+ operation.TurnIntoCopy(Const(value));
+ }
+
+ private static void BitfieldExtractU32(Operation operation)
+ {
+ operation.TurnIntoCopy(Const(GetBitfieldExtractValue(operation)));
+ }
+
+ private static int GetBitfieldExtractValue(Operation operation)
+ {
+ int value = operation.GetSource(0).Value;
+ int lsb = operation.GetSource(1).Value;
+ int length = operation.GetSource(2).Value;
+
+ return value.Extract(lsb, length);
+ }
+
+ private static void UnpackHalf2x16(Operation operation)
+ {
+ int value = operation.GetSource(0).Value;
+
+ value = (value >> operation.ComponentIndex * 16) & 0xffff;
+
+ operation.TurnIntoCopy(ConstF(HalfConversion.HalfToSingle(value)));
+ }
+
+ private static void FPNegate(Operation operation)
+ {
+ float value = operation.GetSource(0).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(-value));
+ }
+
+ private static void EvaluateUnary(Operation operation, Func<int, int> op)
+ {
+ int x = operation.GetSource(0).Value;
+
+ operation.TurnIntoCopy(Const(op(x)));
+ }
+
+ private static void EvaluateFPUnary(Operation operation, Func<float, float> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(op(x)));
+ }
+
+ private static void EvaluateFPUnary(Operation operation, Func<float, bool> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+
+ operation.TurnIntoCopy(Const(op(x) ? IrConsts.True : IrConsts.False));
+ }
+
+ private static void EvaluateBinary(Operation operation, Func<int, int, int> op)
+ {
+ int x = operation.GetSource(0).Value;
+ int y = operation.GetSource(1).Value;
+
+ operation.TurnIntoCopy(Const(op(x, y)));
+ }
+
+ private static void EvaluateBinary(Operation operation, Func<int, int, bool> op)
+ {
+ int x = operation.GetSource(0).Value;
+ int y = operation.GetSource(1).Value;
+
+ operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False));
+ }
+
+ private static void EvaluateFPBinary(Operation operation, Func<float, float, float> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+ float y = operation.GetSource(1).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(op(x, y)));
+ }
+
+ private static void EvaluateFPBinary(Operation operation, Func<float, float, bool> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+ float y = operation.GetSource(1).AsFloat();
+
+ operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False));
+ }
+
+ private static void EvaluateTernary(Operation operation, Func<int, int, int, int> op)
+ {
+ int x = operation.GetSource(0).Value;
+ int y = operation.GetSource(1).Value;
+ int z = operation.GetSource(2).Value;
+
+ operation.TurnIntoCopy(Const(op(x, y, z)));
+ }
+
+ private static void EvaluateFPTernary(Operation operation, Func<float, float, float, float> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+ float y = operation.GetSource(1).AsFloat();
+ float z = operation.GetSource(2).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(op(x, y, z)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs
new file mode 100644
index 00000000..9ef35abc
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs
@@ -0,0 +1,47 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class HalfConversion
+ {
+ public static float HalfToSingle(int value)
+ {
+ int mantissa = (value >> 0) & 0x3ff;
+ int exponent = (value >> 10) & 0x1f;
+ int sign = (value >> 15) & 0x1;
+
+ if (exponent == 0x1f)
+ {
+ //NaN or Infinity.
+ mantissa <<= 13;
+ exponent = 0xff;
+ }
+ else if (exponent != 0 || mantissa != 0 )
+ {
+ if (exponent == 0)
+ {
+ //Denormal.
+ int e = -1;
+ int m = mantissa;
+
+ do
+ {
+ e++;
+ m <<= 1;
+ }
+ while ((m & 0x400) == 0);
+
+ mantissa = m & 0x3ff;
+ exponent = e;
+ }
+
+ mantissa <<= 13;
+ exponent = 127 - 15 + exponent;
+ }
+
+ int output = (sign << 31) | (exponent << 23) | mantissa;
+
+ return BitConverter.Int32BitsToSingle(output);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs
new file mode 100644
index 00000000..88118e3a
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs
@@ -0,0 +1,172 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class Optimizer
+ {
+ public static void Optimize(BasicBlock[] blocks)
+ {
+ bool modified;
+
+ do
+ {
+ modified = false;
+
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ LinkedListNode<INode> node = block.Operations.First;
+
+ while (node != null)
+ {
+ LinkedListNode<INode> nextNode = node.Next;
+
+ bool isUnused = IsUnused(node.Value);
+
+ if (!(node.Value is Operation operation) || isUnused)
+ {
+ if (isUnused)
+ {
+ RemoveNode(block, node);
+
+ modified = true;
+ }
+
+ node = nextNode;
+
+ continue;
+ }
+
+ ConstantFolding.Fold(operation);
+
+ Simplification.Simplify(operation);
+
+ if (DestIsLocalVar(operation))
+ {
+ if (operation.Inst == Instruction.Copy)
+ {
+ PropagateCopy(operation);
+
+ RemoveNode(block, node);
+
+ modified = true;
+ }
+ else if (operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation))
+ {
+ if (operation.Dest.UseOps.Count == 0)
+ {
+ RemoveNode(block, node);
+ }
+
+ modified = true;
+ }
+ }
+
+ node = nextNode;
+ }
+
+ if (BranchElimination.Eliminate(block))
+ {
+ RemoveNode(block, block.Operations.Last);
+
+ modified = true;
+ }
+ }
+ }
+ while (modified);
+ }
+
+ private static void PropagateCopy(Operation copyOp)
+ {
+ //Propagate copy source operand to all uses of
+ //the destination operand.
+ Operand dest = copyOp.Dest;
+ Operand src = copyOp.GetSource(0);
+
+ INode[] uses = dest.UseOps.ToArray();
+
+ foreach (INode useNode in uses)
+ {
+ for (int index = 0; index < useNode.SourcesCount; index++)
+ {
+ if (useNode.GetSource(index) == dest)
+ {
+ useNode.SetSource(index, src);
+ }
+ }
+ }
+ }
+
+ private static bool PropagatePack(Operation packOp)
+ {
+ //Propagate pack source operands to uses by unpack
+ //instruction. The source depends on the unpack instruction.
+ bool modified = false;
+
+ Operand dest = packOp.Dest;
+ Operand src0 = packOp.GetSource(0);
+ Operand src1 = packOp.GetSource(1);
+
+ INode[] uses = dest.UseOps.ToArray();
+
+ foreach (INode useNode in uses)
+ {
+ if (!(useNode is Operation operation) || operation.Inst != Instruction.UnpackHalf2x16)
+ {
+ continue;
+ }
+
+ if (operation.GetSource(0) == dest)
+ {
+ operation.TurnIntoCopy(operation.ComponentIndex == 1 ? src1 : src0);
+
+ modified = true;
+ }
+ }
+
+ return modified;
+ }
+
+ private static void RemoveNode(BasicBlock block, LinkedListNode<INode> 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<INode> nodes = new Queue<INode>();
+
+ nodes.Enqueue(llNode.Value);
+
+ while (nodes.TryDequeue(out INode node))
+ {
+ for (int index = 0; index < node.SourcesCount; index++)
+ {
+ Operand src = node.GetSource(index);
+
+ if (src.Type != OperandType.LocalVariable)
+ {
+ continue;
+ }
+
+ if (src.UseOps.Remove(node) && src.UseOps.Count == 0)
+ {
+ nodes.Enqueue(src.AsgOp);
+ }
+ }
+ }
+ }
+
+ private static bool IsUnused(INode node)
+ {
+ return DestIsLocalVar(node) && node.Dest.UseOps.Count == 0;
+ }
+
+ private static bool DestIsLocalVar(INode node)
+ {
+ return node.Dest != null && node.Dest.Type == OperandType.LocalVariable;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs
new file mode 100644
index 00000000..56b1543f
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs
@@ -0,0 +1,147 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class Simplification
+ {
+ private const int AllOnes = ~0;
+
+ public static void Simplify(Operation operation)
+ {
+ switch (operation.Inst)
+ {
+ case Instruction.Add:
+ case Instruction.BitwiseExclusiveOr:
+ TryEliminateBinaryOpComutative(operation, 0);
+ break;
+
+ case Instruction.BitwiseAnd:
+ TryEliminateBitwiseAnd(operation);
+ break;
+
+ case Instruction.BitwiseOr:
+ TryEliminateBitwiseOr(operation);
+ break;
+
+ case Instruction.ConditionalSelect:
+ TryEliminateConditionalSelect(operation);
+ break;
+
+ case Instruction.Divide:
+ TryEliminateBinaryOpY(operation, 1);
+ break;
+
+ case Instruction.Multiply:
+ TryEliminateBinaryOpComutative(operation, 1);
+ break;
+
+ case Instruction.ShiftLeft:
+ case Instruction.ShiftRightS32:
+ case Instruction.ShiftRightU32:
+ case Instruction.Subtract:
+ TryEliminateBinaryOpY(operation, 0);
+ break;
+ }
+ }
+
+ private static void TryEliminateBitwiseAnd(Operation operation)
+ {
+ //Try to recognize and optimize those 3 patterns (in order):
+ //x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y,
+ //x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(x, AllOnes))
+ {
+ operation.TurnIntoCopy(y);
+ }
+ else if (IsConstEqual(y, AllOnes))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ else if (IsConstEqual(x, 0) || IsConstEqual(y, 0))
+ {
+ operation.TurnIntoCopy(Const(0));
+ }
+ }
+
+ private static void TryEliminateBitwiseOr(Operation operation)
+ {
+ //Try to recognize and optimize those 3 patterns (in order):
+ //x | 0x00000000 == x, 0x00000000 | y == y,
+ //x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(x, 0))
+ {
+ operation.TurnIntoCopy(y);
+ }
+ else if (IsConstEqual(y, 0))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ else if (IsConstEqual(x, AllOnes) || IsConstEqual(y, AllOnes))
+ {
+ operation.TurnIntoCopy(Const(AllOnes));
+ }
+ }
+
+ private static void TryEliminateBinaryOpY(Operation operation, int comparand)
+ {
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(y, comparand))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ }
+
+ private static void TryEliminateBinaryOpComutative(Operation operation, int comparand)
+ {
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(x, comparand))
+ {
+ operation.TurnIntoCopy(y);
+ }
+ else if (IsConstEqual(y, comparand))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ }
+
+ private static void TryEliminateConditionalSelect(Operation operation)
+ {
+ Operand cond = operation.GetSource(0);
+
+ if (cond.Type != OperandType.Constant)
+ {
+ return;
+ }
+
+ //The condition is constant, we can turn it into a copy, and select
+ //the source based on the condition value.
+ int srcIndex = cond.Value != 0 ? 1 : 2;
+
+ Operand source = operation.GetSource(srcIndex);
+
+ operation.TurnIntoCopy(source);
+ }
+
+ private static bool IsConstEqual(Operand operand, int comparand)
+ {
+ if (operand.Type != OperandType.Constant)
+ {
+ return false;
+ }
+
+ return operand.Value == comparand;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Ssa.cs b/Ryujinx.Graphics/Shader/Translation/Ssa.cs
new file mode 100644
index 00000000..b612649c
--- /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<Register, Operand> _map;
+
+ private long[] _phiMasks;
+
+ public DefMap()
+ {
+ _map = new Dictionary<Register, Operand>();
+
+ _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<BasicBlock> dfPhiBlocks = new Queue<BasicBlock>();
+
+ //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<INode> 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<INode> 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<BasicBlock> 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<INode> 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 00000000..706f3cfa
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Translator.cs
@@ -0,0 +1,219 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.CodeGen.Glsl;
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.Instructions;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using Ryujinx.Graphics.Shader.Translation.Optimizations;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ public static class Translator
+ {
+ public static ShaderProgram Translate(IGalMemory memory, ulong address, ShaderConfig config)
+ {
+ return Translate(memory, address, 0, config);
+ }
+
+ public static ShaderProgram Translate(
+ IGalMemory memory,
+ ulong address,
+ ulong addressB,
+ ShaderConfig config)
+ {
+ Operation[] shaderOps = DecodeShader(memory, address, config.Type);
+
+ if (addressB != 0)
+ {
+ //Dual vertex shader.
+ Operation[] shaderOpsB = DecodeShader(memory, addressB, config.Type);
+
+ shaderOps = Combine(shaderOps, shaderOpsB);
+ }
+
+ BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(shaderOps);
+
+ Dominance.FindDominators(irBlocks[0], irBlocks.Length);
+
+ Dominance.FindDominanceFrontiers(irBlocks);
+
+ Ssa.Rename(irBlocks);
+
+ Optimizer.Optimize(irBlocks);
+
+ StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(irBlocks);
+
+ GlslProgram program = GlslGenerator.Generate(sInfo, config);
+
+ ShaderProgramInfo spInfo = new ShaderProgramInfo(
+ program.CBufferDescriptors,
+ program.TextureDescriptors);
+
+ return new ShaderProgram(spInfo, program.Code);
+ }
+
+ private static Operation[] DecodeShader(IGalMemory memory, ulong address, GalShaderType shaderType)
+ {
+ ShaderHeader header = new ShaderHeader(memory, address);
+
+ Block[] cfg = Decoder.Decode(memory, address);
+
+ EmitterContext context = new EmitterContext(shaderType, header);
+
+ for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++)
+ {
+ Block block = cfg[blkIndex];
+
+ context.CurrBlock = block;
+
+ context.MarkLabel(context.GetLabel(block.Address));
+
+ for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++)
+ {
+ OpCode op = block.OpCodes[opIndex];
+
+ if (op.NeverExecute)
+ {
+ continue;
+ }
+
+ Operand predSkipLbl = null;
+
+ bool skipPredicateCheck = op.Emitter == InstEmit.Bra;
+
+ if (op is OpCodeSync opSync)
+ {
+ //If the instruction is a SYNC instruction with only one
+ //possible target address, then the instruction is basically
+ //just a simple branch, we can generate code similar to branch
+ //instructions, with the condition check on the branch itself.
+ skipPredicateCheck |= opSync.Targets.Count < 2;
+ }
+
+ if (!(op.Predicate.IsPT || skipPredicateCheck))
+ {
+ Operand label;
+
+ if (opIndex == block.OpCodes.Count - 1 && block.Next != null)
+ {
+ label = context.GetLabel(block.Next.Address);
+ }
+ else
+ {
+ label = Label();
+
+ predSkipLbl = label;
+ }
+
+ Operand pred = Register(op.Predicate);
+
+ if (op.InvertPredicate)
+ {
+ context.BranchIfTrue(label, pred);
+ }
+ else
+ {
+ context.BranchIfFalse(label, pred);
+ }
+ }
+
+ context.CurrOp = op;
+
+ op.Emitter(context);
+
+ if (predSkipLbl != null)
+ {
+ context.MarkLabel(predSkipLbl);
+ }
+ }
+ }
+
+ return context.GetOperations();
+ }
+
+ private static Operation[] Combine(Operation[] a, Operation[] b)
+ {
+ //Here we combine two shaders.
+ //For shader A:
+ //- All user attribute stores on shader A are turned into copies to a
+ //temporary variable. It's assumed that shader B will consume them.
+ //- All return instructions are turned into branch instructions, the
+ //branch target being the start of the shader B code.
+ //For shader B:
+ //- All user attribute loads on shader B are turned into copies from a
+ //temporary variable, as long that attribute is written by shader A.
+ List<Operation> output = new List<Operation>(a.Length + b.Length);
+
+ Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4];
+
+ Operand lblB = Label();
+
+ for (int index = 0; index < a.Length; index++)
+ {
+ Operation operation = a[index];
+
+ if (IsUserAttribute(operation.Dest))
+ {
+ int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4;
+
+ Operand temp = temps[tIndex];
+
+ if (temp == null)
+ {
+ temp = Local();
+
+ temps[tIndex] = temp;
+ }
+
+ operation.Dest = temp;
+ }
+
+ if (operation.Inst == Instruction.Return)
+ {
+ output.Add(new Operation(Instruction.Branch, lblB));
+ }
+ else
+ {
+ output.Add(operation);
+ }
+ }
+
+ output.Add(new Operation(Instruction.MarkLabel, lblB));
+
+ for (int index = 0; index < b.Length; index++)
+ {
+ Operation operation = b[index];
+
+ for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
+ {
+ Operand src = operation.GetSource(srcIndex);
+
+ if (IsUserAttribute(src))
+ {
+ Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4];
+
+ if (temp != null)
+ {
+ operation.SetSource(srcIndex, temp);
+ }
+ }
+ }
+
+ output.Add(operation);
+ }
+
+ return output.ToArray();
+ }
+
+ private static bool IsUserAttribute(Operand operand)
+ {
+ return operand != null &&
+ operand.Type == OperandType.Attribute &&
+ operand.Value >= AttributeConsts.UserAttributeBase &&
+ operand.Value < AttributeConsts.UserAttributeEnd;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Texture/TextureInstructionSuffix.cs b/Ryujinx.Graphics/Texture/TextureInstructionSuffix.cs
deleted file mode 100644
index 65a8f356..00000000
--- a/Ryujinx.Graphics/Texture/TextureInstructionSuffix.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Texture
-{
- [Flags]
- public enum TextureInstructionSuffix
- {
- None = 0x00, // No Modifier
- Lz = 0x02, // Load LOD Zero
- Lb = 0x08, // Load Bias
- Ll = 0x10, // Load LOD
- Lba = 0x20, // Load Bias with OperA? Auto?
- Lla = 0x40, // Load LOD with OperA? Auto?
- Dc = 0x80, // Depth Compare
- AOffI = 0x100, // Offset
- Mz = 0x200, // Multisample Zero?
- Ptp = 0x400 // ???
- }
-}
diff --git a/Ryujinx.ShaderTools/Memory.cs b/Ryujinx.ShaderTools/Memory.cs
index f801ab39..c99224b5 100644
--- a/Ryujinx.ShaderTools/Memory.cs
+++ b/Ryujinx.ShaderTools/Memory.cs
@@ -23,4 +23,4 @@ namespace Ryujinx.ShaderTools
return Reader.ReadInt32();
}
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs
index 77aba0ab..e763e2c1 100644
--- a/Ryujinx.ShaderTools/Program.cs
+++ b/Ryujinx.ShaderTools/Program.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.Gal;
-using Ryujinx.Graphics.Gal.Shader;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
using System;
using System.IO;
@@ -7,32 +8,30 @@ namespace Ryujinx.ShaderTools
{
class Program
{
- private static readonly int MaxUboSize = 65536;
-
static void Main(string[] args)
{
if (args.Length == 2)
{
- GlslDecompiler Decompiler = new GlslDecompiler(MaxUboSize, true);
-
- GalShaderType ShaderType = GalShaderType.Vertex;
+ GalShaderType type = GalShaderType.Vertex;
switch (args[0].ToLower())
{
- case "v": ShaderType = GalShaderType.Vertex; break;
- case "tc": ShaderType = GalShaderType.TessControl; break;
- case "te": ShaderType = GalShaderType.TessEvaluation; break;
- case "g": ShaderType = GalShaderType.Geometry; break;
- case "f": ShaderType = GalShaderType.Fragment; break;
+ case "v": type = GalShaderType.Vertex; break;
+ case "tc": type = GalShaderType.TessControl; break;
+ case "te": type = GalShaderType.TessEvaluation; break;
+ case "g": type = GalShaderType.Geometry; break;
+ case "f": type = GalShaderType.Fragment; break;
}
- using (FileStream FS = new FileStream(args[1], FileMode.Open, FileAccess.Read))
+ using (FileStream fs = new FileStream(args[1], FileMode.Open, FileAccess.Read))
{
- Memory Mem = new Memory(FS);
+ Memory mem = new Memory(fs);
+
+ ShaderConfig config = new ShaderConfig(type, 65536);
- GlslProgram Program = Decompiler.Decompile(Mem, 0, ShaderType);
+ string code = Translator.Translate(mem, 0, config).Code;
- Console.WriteLine(Program.Code);
+ Console.WriteLine(code);
}
}
else
@@ -41,4 +40,4 @@ namespace Ryujinx.ShaderTools
}
}
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index 1b26b391..42a6a741 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");
}