diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs index 79e77c0a49..06f3fac979 100644 --- a/Ryujinx.Graphics/Gal/IGalShader.cs +++ b/Ryujinx.Graphics/Gal/IGalShader.cs @@ -6,6 +6,8 @@ namespace Ryujinx.Graphics.Gal { void Create(IGalMemory Memory, long Key, GalShaderType Type); + void Create(IGalMemory Memory, long VpAPos, long Key, GalShaderType Type); + IEnumerable GetTextureUsage(long Key); void SetConstBuffer(long Key, int Cbuf, byte[] Data); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs index da2762d6c8..3c5c874eaa 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs @@ -97,12 +97,37 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void Create(IGalMemory Memory, long Key, GalShaderType Type) { - Stages.GetOrAdd(Key, (Stage) => ShaderStageFactory(Memory, Key, Type)); + Stages.GetOrAdd(Key, (Stage) => ShaderStageFactory(Memory, Key, 0, false, Type)); } - private ShaderStage ShaderStageFactory(IGalMemory Memory, long Position, GalShaderType Type) + public void Create(IGalMemory Memory, long VpAPos, long Key, GalShaderType Type) { - GlslProgram Program = GetGlslProgram(Memory, Position, Type); + Stages.GetOrAdd(Key, (Stage) => ShaderStageFactory(Memory, VpAPos, Key, true, Type)); + } + + private ShaderStage ShaderStageFactory( + IGalMemory Memory, + long Position, + long PositionB, + bool IsDualVp, + GalShaderType Type) + { + GlslProgram Program; + + GlslDecompiler Decompiler = new GlslDecompiler(); + + if (IsDualVp) + { + Program = Decompiler.Decompile( + Memory, + Position + 0x50, + PositionB + 0x50, + Type); + } + else + { + Program = Decompiler.Decompile(Memory, Position + 0x50, Type); + } return new ShaderStage( Type, @@ -111,13 +136,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL Program.Uniforms); } - private GlslProgram GetGlslProgram(IGalMemory Memory, long Position, GalShaderType Type) - { - GlslDecompiler Decompiler = new GlslDecompiler(); - - return Decompiler.Decompile(Memory, Position + 0x50, Type); - } - public IEnumerable GetTextureUsage(long Key) { if (Stages.TryGetValue(Key, out ShaderStage Stage)) diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs index 86838ab2e4..5c7dd794e3 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs @@ -11,31 +11,41 @@ namespace Ryujinx.Graphics.Gal.Shader public const int VertexIdAttr = 0x2fc; public const int GlPositionWAttr = 0x7c; + public const int MaxUboSize = 1024; + + public const int GlPositionVec4Index = 7; + private const int AttrStartIndex = 8; private const int TexStartIndex = 8; public const string PositionOutAttrName = "position"; - private const string InAttrName = "in_attr"; - private const string OutAttrName = "out_attr"; + private const string TextureName = "tex"; private const string UniformName = "c"; - private const string GprName = "gpr"; - private const string PredName = "pred"; - private const string TextureName = "tex"; + 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 FlipUniformName = "flip"; + public const string ProgramName = "program"; + public const string ProgramAName = ProgramName + "_a"; + public const string ProgramBName = ProgramName + "_b"; + private string[] StagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; private string StagePrefix; private Dictionary m_Textures; - private Dictionary m_Uniforms; + private Dictionary m_Attributes; private Dictionary m_InAttributes; private Dictionary m_OutAttributes; @@ -43,9 +53,9 @@ namespace Ryujinx.Graphics.Gal.Shader private Dictionary m_Preds; public IReadOnlyDictionary Textures => m_Textures; - public IReadOnlyDictionary Uniforms => m_Uniforms; + public IReadOnlyDictionary Attributes => m_Attributes; public IReadOnlyDictionary InAttributes => m_InAttributes; public IReadOnlyDictionary OutAttributes => m_OutAttributes; @@ -54,31 +64,28 @@ namespace Ryujinx.Graphics.Gal.Shader public GalShaderType ShaderType { get; private set; } - public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType) + private GlslDecl(GalShaderType ShaderType) { this.ShaderType = ShaderType; - StagePrefix = StagePrefixes[(int)ShaderType] + "_"; - m_Uniforms = new Dictionary(); - m_Textures = new Dictionary(); + m_Attributes = new Dictionary(); m_InAttributes = new Dictionary(); m_OutAttributes = new Dictionary(); m_Gprs = new Dictionary(); m_Preds = new Dictionary(); + } + + public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType) : this(ShaderType) + { + StagePrefix = StagePrefixes[(int)ShaderType] + "_"; if (ShaderType == GalShaderType.Fragment) { m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, 0, 4)); - - m_InAttributes.Add(7, new ShaderDeclInfo(PositionOutAttrName, -1, 0, 4)); - } - else - { - m_OutAttributes.Add(7, new ShaderDeclInfo("gl_Position", -1, 0, 4)); } foreach (ShaderIrBlock Block in Blocks) @@ -90,6 +97,57 @@ namespace Ryujinx.Graphics.Gal.Shader } } + 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_Preds, VpA.m_Preds, VpB.m_Preds); + + //Merge input attributes. + foreach (KeyValuePair KV in VpA.m_InAttributes) + { + Combined.m_InAttributes.TryAdd(KV.Key, KV.Value); + } + + foreach (KeyValuePair KV in VpB.m_InAttributes) + { + //If Vertex Program A already writes to this attribute, + //then we don't need to add it as an input attribute since + //Vertex Program A will already have written to it anyway, + //and there's no guarantee that there is an input attribute + //for this slot. + if (!VpA.m_OutAttributes.ContainsKey(KV.Key)) + { + Combined.m_InAttributes.TryAdd(KV.Key, KV.Value); + } + } + + return Combined; + } + + private static void Merge( + Dictionary C, + Dictionary A, + Dictionary B) + { + foreach (KeyValuePair KV in A) + { + C.TryAdd(KV.Key, KV.Value); + } + + foreach (KeyValuePair KV in B) + { + C.TryAdd(KV.Key, KV.Value); + } + } + private void Traverse(ShaderIrNode Parent, ShaderIrNode Node) { switch (Node) @@ -133,28 +191,14 @@ namespace Ryujinx.Graphics.Gal.Shader case ShaderIrOperCbuf Cbuf: { - if (m_Uniforms.TryGetValue(Cbuf.Index, out ShaderDeclInfo DeclInfo)) - { - DeclInfo.SetCbufOffs(Cbuf.Pos); - } - else + if (!m_Uniforms.ContainsKey(Cbuf.Index)) { string Name = StagePrefix + UniformName + Cbuf.Index; - DeclInfo = new ShaderDeclInfo(Name, Cbuf.Pos, Cbuf.Index); + ShaderDeclInfo DeclInfo = new ShaderDeclInfo(Name, Cbuf.Pos, Cbuf.Index); m_Uniforms.Add(Cbuf.Index, DeclInfo); } - - if (Cbuf.Offs != null) - { - //The constant buffer is being accessed as an array, - //we have no way to know the max element it may access in this case. - //Here, we just assume the array size with arbitrary values. - //TODO: Find a better solution for this. - DeclInfo.SetCbufOffs(Cbuf.Pos + 15); - } - break; } @@ -172,6 +216,11 @@ namespace Ryujinx.Graphics.Gal.Shader int GlslIndex = Index - AttrStartIndex; + if (GlslIndex < 0) + { + return; + } + ShaderDeclInfo DeclInfo; if (Parent is ShaderIrAsg Asg && Asg.Dst == Node) @@ -195,6 +244,12 @@ namespace Ryujinx.Graphics.Gal.Shader DeclInfo.Enlarge(Elem + 1); + if (!m_Attributes.ContainsKey(Index)) + { + DeclInfo = new ShaderDeclInfo(AttrName + GlslIndex, GlslIndex, 0, 4); + + m_Attributes.Add(Index, DeclInfo); + } break; } @@ -224,7 +279,10 @@ namespace Ryujinx.Graphics.Gal.Shader private bool HasName(Dictionary Decls, int Index) { - int VecIndex = Index >> 2; + //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)) { diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs index 576358c79c..2bc350da3e 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.Gal.Shader private GlslDecl Decl; - private ShaderIrBlock[] Blocks; + private ShaderIrBlock[] Blocks, BlocksB; private StringBuilder SB; @@ -104,24 +104,63 @@ namespace Ryujinx.Graphics.Gal.Shader }; } + public GlslProgram Decompile( + IGalMemory Memory, + long VpAPosition, + long VpBPosition, + GalShaderType ShaderType) + { + Blocks = ShaderDecoder.Decode(Memory, VpAPosition); + BlocksB = ShaderDecoder.Decode(Memory, VpBPosition); + + GlslDecl DeclVpA = new GlslDecl(Blocks, ShaderType); + GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType); + + Decl = GlslDecl.Merge(DeclVpA, DeclVpB); + + return Decompile(); + } + public GlslProgram Decompile(IGalMemory Memory, long Position, GalShaderType ShaderType) { - Blocks = ShaderDecoder.Decode(Memory, Position); + Blocks = ShaderDecoder.Decode(Memory, Position); + BlocksB = null; Decl = new GlslDecl(Blocks, ShaderType); + return Decompile(); + } + + private GlslProgram Decompile() + { SB = new StringBuilder(); SB.AppendLine("#version 410 core"); PrintDeclTextures(); PrintDeclUniforms(); + PrintDeclAttributes(); PrintDeclInAttributes(); PrintDeclOutAttributes(); PrintDeclGprs(); PrintDeclPreds(); - PrintBlockScope(Blocks[0], null, null, "void main()", IdentationStr); + if (BlocksB != null) + { + PrintBlockScope(Blocks[0], null, null, "void " + GlslDecl.ProgramAName + "()", IdentationStr); + + SB.AppendLine(); + + PrintBlockScope(BlocksB[0], null, null, "void " + GlslDecl.ProgramBName + "()", IdentationStr); + } + else + { + PrintBlockScope(Blocks[0], null, null, "void " + GlslDecl.ProgramName + "()", IdentationStr); + } + + SB.AppendLine(); + + PrintMain(); string GlslCode = SB.ToString(); @@ -143,11 +182,15 @@ namespace Ryujinx.Graphics.Gal.Shader SB.AppendLine("uniform vec2 " + GlslDecl.FlipUniformName + ";"); } + 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[{DeclInfo.Index / 4 + 1}];"); - SB.AppendLine($"}};"); + + SB.AppendLine($"{IdentationStr}vec4 {DeclInfo.Name}_data[{GlslDecl.MaxUboSize}];"); + + SB.AppendLine("};"); } if (Decl.Uniforms.Count > 0) @@ -156,6 +199,11 @@ namespace Ryujinx.Graphics.Gal.Shader } } + private void PrintDeclAttributes() + { + PrintDecls(Decl.Attributes); + } + private void PrintDeclInAttributes() { if (Decl.ShaderType == GalShaderType.Fragment) @@ -244,6 +292,59 @@ namespace Ryujinx.Graphics.Gal.Shader return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name; } + private void PrintMain() + { + SB.AppendLine("void main() {"); + + foreach (KeyValuePair KV in Decl.InAttributes) + { + if (!Decl.Attributes.TryGetValue(KV.Key, out ShaderDeclInfo Attr)) + { + continue; + } + + ShaderDeclInfo DeclInfo = KV.Value; + + string Swizzle = ".xyzw".Substring(0, DeclInfo.Size + 1); + + SB.AppendLine(IdentationStr + Attr.Name + Swizzle + " = " + DeclInfo.Name + ";"); + } + + if (BlocksB != null) + { + SB.AppendLine(IdentationStr + GlslDecl.ProgramAName + "();"); + SB.AppendLine(IdentationStr + GlslDecl.ProgramBName + "();"); + } + else + { + SB.AppendLine(IdentationStr + GlslDecl.ProgramName + "();"); + } + + foreach (KeyValuePair KV in Decl.OutAttributes) + { + if (!Decl.Attributes.TryGetValue(KV.Key, out ShaderDeclInfo Attr)) + { + continue; + } + + ShaderDeclInfo DeclInfo = KV.Value; + + string Swizzle = ".xyzw".Substring(0, DeclInfo.Size + 1); + + SB.AppendLine(IdentationStr + DeclInfo.Name + " = " + Attr.Name + Swizzle + ";"); + } + + if (Decl.ShaderType == GalShaderType.Vertex) + { + SB.AppendLine(IdentationStr + "gl_Position.xy *= " + GlslDecl.FlipUniformName + ";"); + + SB.AppendLine(IdentationStr + GlslDecl.PositionOutAttrName + " = gl_Position;"); + SB.AppendLine(IdentationStr + GlslDecl.PositionOutAttrName + ".w = 1;"); + } + + SB.AppendLine("}"); + } + private void PrintBlockScope( ShaderIrBlock Block, ShaderIrBlock EndBlock, @@ -383,18 +484,6 @@ namespace Ryujinx.Graphics.Gal.Shader continue; } - else if (Op.Inst == ShaderIrInst.Exit) - { - //Do everything that needs to be done before - //the shader ends here. - if (Decl.ShaderType == GalShaderType.Vertex) - { - SB.AppendLine(Identation + "gl_Position.xy *= flip;"); - - SB.AppendLine(Identation + GlslDecl.PositionOutAttrName + " = gl_Position;"); - SB.AppendLine(Identation + GlslDecl.PositionOutAttrName + ".w = 1;"); - } - } SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); } @@ -546,11 +635,12 @@ namespace Ryujinx.Graphics.Gal.Shader private string GetOutAbufName(ShaderIrOperAbuf Abuf) { - return GetName(Decl.OutAttributes, Abuf); + return GetAttrTempName(Abuf); } private string GetName(ShaderIrOperAbuf Abuf) { + //Handle special scalar read-only attributes here. if (Decl.ShaderType == GalShaderType.Vertex) { switch (Abuf.Offs) @@ -569,20 +659,33 @@ namespace Ryujinx.Graphics.Gal.Shader } } - return GetName(Decl.InAttributes, Abuf); + return GetAttrTempName(Abuf); } - private string GetName(IReadOnlyDictionary Dict, ShaderIrOperAbuf Abuf) + private string GetAttrTempName(ShaderIrOperAbuf Abuf) { int Index = Abuf.Offs >> 4; int Elem = (Abuf.Offs >> 2) & 3; - if (!Dict.TryGetValue(Index, out ShaderDeclInfo DeclInfo)) + string Swizzle = "." + GetAttrSwizzle(Elem); + + if (!Decl.Attributes.TryGetValue(Index, out ShaderDeclInfo DeclInfo)) { + //Handle special vec4 attributes here + //(for example, index 7 is aways gl_Position). + if (Index == GlslDecl.GlPositionVec4Index) + { + string Name = + Decl.ShaderType != GalShaderType.Vertex && + Decl.ShaderType != GalShaderType.Geometry ? GlslDecl.PositionOutAttrName : "gl_Position"; + + return Name + Swizzle; + } + throw new InvalidOperationException(); } - return DeclInfo.Size > 1 ? DeclInfo.Name + "." + GetAttrSwizzle(Elem) : DeclInfo.Name; + return DeclInfo.Name + Swizzle; } private string GetName(ShaderIrOperGpr Gpr) diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs index 0763d3caf6..2e022fbd44 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs @@ -31,6 +31,24 @@ namespace Ryujinx.Graphics.Gal.Shader EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fadd); } + public static void Fadd_I32(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode OperA = GetOperGpr8 (OpCode); + ShaderIrNode OperB = GetOperImmf32_20(OpCode); + + bool NegB = ((OpCode >> 53) & 1) != 0; + bool AbsA = ((OpCode >> 54) & 1) != 0; + bool NegA = ((OpCode >> 56) & 1) != 0; + bool AbsB = ((OpCode >> 57) & 1) != 0; + + OperA = GetAluFabsFneg(OperA, AbsA, NegA); + OperB = GetAluFabsFneg(OperB, AbsB, NegB); + + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Fadd, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + public static void Fadd_R(ShaderIrBlock Block, long OpCode) { EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fadd); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs index 89949d62ee..8d0925a321 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs @@ -24,7 +24,14 @@ namespace Ryujinx.Graphics.Gal.Shader public static void Exit(ShaderIrBlock Block, long OpCode) { - Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Exit), OpCode)); + int CCode = (int)OpCode & 0x1f; + + //TODO: Figure out what the other condition codes mean... + if (CCode == 0xf) + { + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Exit), OpCode)); + } + } public static void Kil(ShaderIrBlock Block, long OpCode) diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs index 074cfbb28b..b4f51e5084 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs @@ -39,14 +39,15 @@ namespace Ryujinx.Graphics.Gal.Shader 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("001100101xxxxx", ShaderDecode.Ffma_I); + Set("0011001x1xxxxx", ShaderDecode.Ffma_I); Set("010100011xxxxx", ShaderDecode.Ffma_RC); Set("010110011xxxxx", ShaderDecode.Ffma_RR); - Set("00011110xxxxxx", ShaderDecode.Fmul_I32); 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); diff --git a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs index e5ecee9506..d400850c86 100644 --- a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs +++ b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs @@ -23,13 +23,5 @@ namespace Ryujinx.Graphics.Gal Size = NewSize; } } - - internal void SetCbufOffs(int Offs) - { - if (Index < Offs) - { - Index = Offs; - } - } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs index 1663d4c59b..380082b316 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs +++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs @@ -126,7 +126,33 @@ namespace Ryujinx.HLE.Gpu.Engines long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); - for (int Index = 0; Index < 6; Index++) + int Index = 1; + + int VpAControl = ReadRegister(NvGpuEngine3dReg.ShaderNControl); + + bool VpAEnable = (VpAControl & 1) != 0; + + if (VpAEnable) + { + //Note: The maxwell supports 2 vertex programs, usually + //only VP B is used, but in some cases VP A is also used. + //In this case, it seems to function as an extra vertex + //shader stage. + //The graphics abstraction layer has a special overload for this + //case, which should merge the two shaders into one vertex shader. + int VpAOffset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset); + int VpBOffset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + 0x10); + + long VpAPos = BasePosition + (uint)VpAOffset; + long VpBPos = BasePosition + (uint)VpBOffset; + + Gpu.Renderer.Shader.Create(Vmm, VpAPos, VpBPos, GalShaderType.Vertex); + Gpu.Renderer.Shader.Bind(VpBPos); + + Index = 2; + } + + for (; Index < 6; Index++) { int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + Index * 0x10); int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + Index * 0x10);