From 97ca974213ec9564ed4a9c57e998ca726dbbb64f Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 5 Jul 2018 15:47:29 -0300
Subject: [PATCH] Implement some GPU features (#209)

* Implement stencil testing

* Implement depth testing

* Implement face culling

* Implement front face

* Comparison functions now take OGL enums too

* Fix front facing when flipping was used

* Add depth and stencil clear values
---
 Ryujinx.Graphics/Gal/GalComparisonOp.cs       |  16 +-
 Ryujinx.Graphics/Gal/GalCullFace.cs           |   9 +
 Ryujinx.Graphics/Gal/GalFrontFace.cs          |   8 +
 Ryujinx.Graphics/Gal/GalStencilOp.cs          |  14 ++
 Ryujinx.Graphics/Gal/IGalRasterizer.cs        |  18 ++
 .../Gal/OpenGL/OGLEnumConverter.cs            |  59 +++++++
 Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs  |  53 ++++++
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs      | 157 ++++++++++++++++--
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs   |  18 ++
 9 files changed, 334 insertions(+), 18 deletions(-)
 create mode 100644 Ryujinx.Graphics/Gal/GalCullFace.cs
 create mode 100644 Ryujinx.Graphics/Gal/GalFrontFace.cs
 create mode 100644 Ryujinx.Graphics/Gal/GalStencilOp.cs

diff --git a/Ryujinx.Graphics/Gal/GalComparisonOp.cs b/Ryujinx.Graphics/Gal/GalComparisonOp.cs
index ddddecebb4..f26a775337 100644
--- a/Ryujinx.Graphics/Gal/GalComparisonOp.cs
+++ b/Ryujinx.Graphics/Gal/GalComparisonOp.cs
@@ -2,13 +2,13 @@ namespace Ryujinx.Graphics.Gal
 {
     public enum GalComparisonOp
     {
-        Never    = 0x200,
-        Less     = 0x201,
-        Equal    = 0x202,
-        Lequal   = 0x203,
-        Greater  = 0x204,
-        NotEqual = 0x205,
-        Gequal   = 0x206,
-        Always   = 0x207
+        Never    = 0x1,
+        Less     = 0x2,
+        Equal    = 0x3,
+        Lequal   = 0x4,
+        Greater  = 0x5,
+        NotEqual = 0x6,
+        Gequal   = 0x7,
+        Always   = 0x8
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalCullFace.cs b/Ryujinx.Graphics/Gal/GalCullFace.cs
new file mode 100644
index 0000000000..4ab3e1742c
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalCullFace.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalCullFace
+    {
+        Front        = 0x404,
+        Back         = 0x405,
+        FrontAndBack = 0x408
+    }
+}
diff --git a/Ryujinx.Graphics/Gal/GalFrontFace.cs b/Ryujinx.Graphics/Gal/GalFrontFace.cs
new file mode 100644
index 0000000000..17ad11267b
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalFrontFace.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalFrontFace
+    {
+        CW  = 0x900,
+        CCW = 0x901
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalStencilOp.cs b/Ryujinx.Graphics/Gal/GalStencilOp.cs
new file mode 100644
index 0000000000..fc83ca5ea6
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalStencilOp.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalStencilOp
+    {
+        Keep     = 0x1,
+        Zero     = 0x2,
+        Replace  = 0x3,
+        Incr     = 0x4,
+        Decr     = 0x5,
+        Invert   = 0x6,
+        IncrWrap = 0x7,
+        DecrWrap = 0x8
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs
index e0469382fe..586eae6ba7 100644
--- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs
+++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs
@@ -8,16 +8,34 @@ namespace Ryujinx.Graphics.Gal
 
         bool IsIboCached(long Key, long DataSize);
 
+        void SetFrontFace(GalFrontFace FrontFace);
+
         void EnableCullFace();
 
         void DisableCullFace();
 
+        void SetCullFace(GalCullFace CullFace);
+
         void EnableDepthTest();
 
         void DisableDepthTest();
 
         void SetDepthFunction(GalComparisonOp Func);
 
+        void SetClearDepth(float Depth);
+
+        void EnableStencilTest();
+
+        void DisableStencilTest();
+
+        void SetStencilFunction(bool IsFrontFace, GalComparisonOp Func, int Ref, int Mask);
+
+        void SetStencilOp(bool IsFrontFace, GalStencilOp Fail, GalStencilOp ZFail, GalStencilOp ZPass);
+
+        void SetStencilMask(bool IsFrontFace, int Mask);
+
+        void SetClearStencil(int Stencil);
+
         void CreateVbo(long Key, byte[] Buffer);
 
         void CreateIbo(long Key, byte[] Buffer);
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
index 349c695e5b..3a81150d6f 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
@@ -5,17 +5,76 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 {
     static class OGLEnumConverter
     {
+        public static FrontFaceDirection GetFrontFace(GalFrontFace FrontFace)
+        {
+            switch (FrontFace)
+            {
+                case GalFrontFace.CW:  return FrontFaceDirection.Cw;
+                case GalFrontFace.CCW: return FrontFaceDirection.Ccw;
+            }
+
+            throw new ArgumentException(nameof(FrontFace));
+        }
+
+        public static CullFaceMode GetCullFace(GalCullFace CullFace)
+        {
+            switch (CullFace)
+            {
+                case GalCullFace.Front:        return CullFaceMode.Front;
+                case GalCullFace.Back:         return CullFaceMode.Back;
+                case GalCullFace.FrontAndBack: return CullFaceMode.FrontAndBack;
+            }
+
+            throw new ArgumentException(nameof(CullFace));
+        }
+
+        public static StencilOp GetStencilOp(GalStencilOp Op)
+        {
+            switch (Op)
+            {
+                case GalStencilOp.Keep:     return StencilOp.Keep;
+                case GalStencilOp.Zero:     return StencilOp.Zero;
+                case GalStencilOp.Replace:  return StencilOp.Replace;
+                case GalStencilOp.Incr:     return StencilOp.Incr;
+                case GalStencilOp.Decr:     return StencilOp.Decr;
+                case GalStencilOp.Invert:   return StencilOp.Invert;
+                case GalStencilOp.IncrWrap: return StencilOp.IncrWrap;
+                case GalStencilOp.DecrWrap: return StencilOp.DecrWrap;
+            }
+
+            throw new ArgumentException(nameof(Op));
+        }
+
         public static DepthFunction GetDepthFunc(GalComparisonOp Func)
         {
+            //Looks like the GPU can take it's own values (described in GalComparisonOp) and OpenGL values alike
             if ((int)Func >= (int)DepthFunction.Never &&
                 (int)Func <= (int)DepthFunction.Always)
             {
                 return (DepthFunction)Func;
             }
 
+            switch (Func)
+            {
+                case GalComparisonOp.Never:    return DepthFunction.Never;
+                case GalComparisonOp.Less:     return DepthFunction.Less;
+                case GalComparisonOp.Equal:    return DepthFunction.Equal;
+                case GalComparisonOp.Lequal:   return DepthFunction.Lequal;
+                case GalComparisonOp.Greater:  return DepthFunction.Greater;
+                case GalComparisonOp.NotEqual: return DepthFunction.Notequal;
+                case GalComparisonOp.Gequal:   return DepthFunction.Gequal;
+                case GalComparisonOp.Always:   return DepthFunction.Always;
+            }
+
             throw new ArgumentException(nameof(Func));
         }
 
+        public static StencilFunction GetStencilFunc(GalComparisonOp Func)
+        {
+            //OGL comparison values match, it's just an enum cast
+            return (StencilFunction)GetDepthFunc(Func);
+        }
+
         public static DrawElementsType GetDrawElementsType(GalIndexFormat Format)
         {
             switch (Format)
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
index 8bff6bb3e0..b988571172 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
@@ -106,6 +106,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             return IboCache.TryGetSize(Key, out long Size) && Size == DataSize;
         }
 
+        public void SetFrontFace(GalFrontFace FrontFace)
+        {
+            GL.FrontFace(OGLEnumConverter.GetFrontFace(FrontFace));
+        }
+
         public void EnableCullFace()
         {
             GL.Enable(EnableCap.CullFace);
@@ -116,6 +121,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             GL.Disable(EnableCap.CullFace);
         }
 
+        public void SetCullFace(GalCullFace CullFace)
+        {
+            GL.CullFace(OGLEnumConverter.GetCullFace(CullFace));
+        }
+
         public void EnableDepthTest()
         {
             GL.Enable(EnableCap.DepthTest);
@@ -131,6 +141,49 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             GL.DepthFunc(OGLEnumConverter.GetDepthFunc(Func));
         }
 
+        public void SetClearDepth(float Depth)
+        {
+            GL.ClearDepth(Depth);
+        }
+
+        public void EnableStencilTest()
+        {
+            GL.Enable(EnableCap.StencilTest);
+        }
+
+        public void DisableStencilTest()
+        {
+            GL.Disable(EnableCap.StencilTest);
+        }
+
+        public void SetStencilFunction(bool IsFrontFace, GalComparisonOp Func, int Ref, int Mask)
+        {
+            GL.StencilFuncSeparate(
+                IsFrontFace ? StencilFace.Front : StencilFace.Back,
+                OGLEnumConverter.GetStencilFunc(Func),
+                Ref,
+                Mask);
+        }
+
+        public void SetStencilOp(bool IsFrontFace, GalStencilOp Fail, GalStencilOp ZFail, GalStencilOp ZPass)
+        {
+            GL.StencilOpSeparate(
+                IsFrontFace ? StencilFace.Front : StencilFace.Back,
+                OGLEnumConverter.GetStencilOp(Fail),
+                OGLEnumConverter.GetStencilOp(ZFail),
+                OGLEnumConverter.GetStencilOp(ZPass));
+        }
+
+        public void SetStencilMask(bool IsFrontFace, int Mask)
+        {
+            GL.StencilMaskSeparate(IsFrontFace ? StencilFace.Front : StencilFace.Back, Mask);
+        }
+
+        public void SetClearStencil(int Stencil)
+        {
+            GL.ClearStencil(Stencil);
+        }
+
         public void CreateVbo(long Key, byte[] Buffer)
         {
             int Handle = GL.GenBuffer();
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
index b27f5c142b..e0e769d486 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
@@ -79,8 +79,10 @@ namespace Ryujinx.HLE.Gpu.Engines
 
             Gpu.Renderer.Shader.BindProgram();
 
+            SetFrontFace();
             SetCullFace();
             SetDepth();
+            SetStencil();
             SetAlphaBlending();
 
             UploadTextures(Vmm, Keys);
@@ -173,14 +175,8 @@ namespace Ryujinx.HLE.Gpu.Engines
                 Gpu.Renderer.Shader.Bind(Key);
             }
 
-            int RawSX = ReadRegister(NvGpuEngine3dReg.ViewportScaleX);
-            int RawSY = ReadRegister(NvGpuEngine3dReg.ViewportScaleY);
-
-            float SX = BitConverter.Int32BitsToSingle(RawSX);
-            float SY = BitConverter.Int32BitsToSingle(RawSY);
-
-            float SignX = MathF.Sign(SX);
-            float SignY = MathF.Sign(SY);
+            float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportScaleX);
+            float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportScaleY);
 
             Gpu.Renderer.Shader.SetFlip(SignX, SignY);
 
@@ -202,14 +198,145 @@ namespace Ryujinx.HLE.Gpu.Engines
             throw new ArgumentOutOfRangeException(nameof(Program));
         }
 
+        private void SetFrontFace()
+        {
+            float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportScaleX);
+            float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportScaleY);
+
+            GalFrontFace FrontFace = (GalFrontFace)ReadRegister(NvGpuEngine3dReg.FrontFace);
+
+            //Flipping breaks facing. Flipping front facing too fixes it
+            if (SignX != SignY)
+            {
+                switch (FrontFace)
+                {
+                    case GalFrontFace.CW:
+                        FrontFace = GalFrontFace.CCW;
+                        break;
+
+                    case GalFrontFace.CCW:
+                        FrontFace = GalFrontFace.CW;
+                        break;
+                }
+            }
+
+            Gpu.Renderer.Rasterizer.SetFrontFace(FrontFace);
+        }
+
         private void SetCullFace()
         {
-            //TODO.
+            bool Enable = (ReadRegister(NvGpuEngine3dReg.CullFaceEnable) & 1) != 0;
+
+            if (Enable)
+            {
+                Gpu.Renderer.Rasterizer.EnableCullFace();
+            }
+            else
+            {
+                Gpu.Renderer.Rasterizer.DisableCullFace();
+            }
+
+            if (!Enable)
+            {
+                return;
+            }
+
+            GalCullFace CullFace = (GalCullFace)ReadRegister(NvGpuEngine3dReg.CullFace);
+
+            Gpu.Renderer.Rasterizer.SetCullFace(CullFace);
         }
 
         private void SetDepth()
         {
-            //TODO.
+            float ClearDepth = ReadRegisterFloat(NvGpuEngine3dReg.ClearDepth);
+
+            Gpu.Renderer.Rasterizer.SetClearDepth(ClearDepth);
+
+            bool Enable = (ReadRegister(NvGpuEngine3dReg.DepthTestEnable) & 1) != 0;
+
+            if (Enable)
+            {
+                Gpu.Renderer.Rasterizer.EnableDepthTest();
+            }
+            else
+            {
+                Gpu.Renderer.Rasterizer.DisableDepthTest();
+            }
+
+            if (!Enable)
+            {
+                return;
+            }
+
+            GalComparisonOp Func = (GalComparisonOp)ReadRegister(NvGpuEngine3dReg.DepthTestFunction);
+
+            Gpu.Renderer.Rasterizer.SetDepthFunction(Func);
+        }
+
+        private void SetStencil()
+        {
+            int ClearStencil = ReadRegister(NvGpuEngine3dReg.ClearStencil);
+
+            Gpu.Renderer.Rasterizer.SetClearStencil(ClearStencil);
+
+            bool Enable = (ReadRegister(NvGpuEngine3dReg.StencilEnable) & 1) != 0;
+
+            if (Enable)
+            {
+                Gpu.Renderer.Rasterizer.EnableStencilTest();
+            }
+            else
+            {
+                Gpu.Renderer.Rasterizer.DisableStencilTest();
+            }
+
+            if (!Enable)
+            {
+                return;
+            }
+
+            void SetFaceStencil(
+                bool IsFrontFace,
+                NvGpuEngine3dReg Func,
+                NvGpuEngine3dReg FuncRef,
+                NvGpuEngine3dReg FuncMask,
+                NvGpuEngine3dReg OpFail,
+                NvGpuEngine3dReg OpZFail,
+                NvGpuEngine3dReg OpZPass,
+                NvGpuEngine3dReg Mask)
+            {
+                Gpu.Renderer.Rasterizer.SetStencilFunction(
+                    IsFrontFace,
+                    (GalComparisonOp)ReadRegister(Func),
+                    ReadRegister(FuncRef),
+                    ReadRegister(FuncMask));
+
+                Gpu.Renderer.Rasterizer.SetStencilOp(
+                    IsFrontFace,
+                    (GalStencilOp)ReadRegister(OpFail),
+                    (GalStencilOp)ReadRegister(OpZFail),
+                    (GalStencilOp)ReadRegister(OpZPass));
+
+                Gpu.Renderer.Rasterizer.SetStencilMask(IsFrontFace, ReadRegister(Mask));
+            }
+
+            SetFaceStencil(false,
+                NvGpuEngine3dReg.StencilBackFuncFunc,
+                NvGpuEngine3dReg.StencilBackFuncRef,
+                NvGpuEngine3dReg.StencilBackFuncMask,
+                NvGpuEngine3dReg.StencilBackOpFail,
+                NvGpuEngine3dReg.StencilBackOpZFail,
+                NvGpuEngine3dReg.StencilBackOpZPass,
+                NvGpuEngine3dReg.StencilBackMask);
+
+            SetFaceStencil(true,
+                NvGpuEngine3dReg.StencilFrontFuncFunc,
+                NvGpuEngine3dReg.StencilFrontFuncRef,
+                NvGpuEngine3dReg.StencilFrontFuncMask,
+                NvGpuEngine3dReg.StencilFrontOpFail,
+                NvGpuEngine3dReg.StencilFrontOpZFail,
+                NvGpuEngine3dReg.StencilFrontOpZPass,
+                NvGpuEngine3dReg.StencilFrontMask);
         }
 
         private void SetAlphaBlending()
@@ -549,6 +676,11 @@ namespace Ryujinx.HLE.Gpu.Engines
             ConstBuffers[Stage][Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferSize);
         }
 
+        private float GetFlipSign(NvGpuEngine3dReg Reg)
+        {
+            return MathF.Sign(ReadRegisterFloat(Reg));
+        }
+
         private long MakeInt64From2xInt32(NvGpuEngine3dReg Reg)
         {
             return
@@ -571,6 +703,11 @@ namespace Ryujinx.HLE.Gpu.Engines
             return Registers[(int)Reg];
         }
 
+        private float ReadRegisterFloat(NvGpuEngine3dReg Reg)
+        {
+            return BitConverter.Int32BitsToSingle(ReadRegister(Reg));
+        }
+
         private void WriteRegister(NvGpuEngine3dReg Reg, int Value)
         {
             Registers[(int)Reg] = Value;
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
index 64866ce9aa..9eb2966d9e 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
@@ -14,6 +14,11 @@ namespace Ryujinx.HLE.Gpu.Engines
         ViewportTranslateZ   = 0x285,
         VertexArrayFirst     = 0x35d,
         VertexArrayCount     = 0x35e,
+        ClearDepth           = 0x364,
+        ClearStencil         = 0x368,
+        StencilBackFuncRef   = 0x3d5,
+        StencilBackMask      = 0x3d6,
+        StencilBackFuncMask  = 0x3d7,
         VertexAttribNFormat  = 0x458,
         DepthTestEnable      = 0x4b3,
         IBlendEnable         = 0x4b9,
@@ -27,9 +32,22 @@ namespace Ryujinx.HLE.Gpu.Engines
         BlendFuncDstAlpha    = 0x4d6,
         BlendEnableMaster    = 0x4d7,
         IBlendNEnable        = 0x4d8,
+        StencilEnable        = 0x4e0,
+        StencilFrontOpFail   = 0x4e1,
+        StencilFrontOpZFail  = 0x4e2,
+        StencilFrontOpZPass  = 0x4e3,
+        StencilFrontFuncFunc = 0x4e4,
+        StencilFrontFuncRef  = 0x4e5,
+        StencilFrontFuncMask = 0x4e6,
+        StencilFrontMask     = 0x4e7,
         VertexArrayElemBase  = 0x50d,
         TexHeaderPoolOffset  = 0x55d,
         TexSamplerPoolOffset = 0x557,
+        StencilTwoSideEnable = 0x565,
+        StencilBackOpFail    = 0x566,
+        StencilBackOpZFail   = 0x567,
+        StencilBackOpZPass   = 0x568,
+        StencilBackFuncFunc  = 0x569,
         ShaderAddress        = 0x582,
         VertexBeginGl        = 0x586,
         IndexArrayAddress    = 0x5f2,