From 97ca974213ec9564ed4a9c57e998ca726dbbb64f Mon Sep 17 00:00:00 2001 From: ReinUsesLisp 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 ddddecebb..f26a77533 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 000000000..4ab3e1742 --- /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 000000000..17ad11267 --- /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 000000000..fc83ca5ea --- /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 e0469382f..586eae6ba 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 349c695e5..3a81150d6 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 8bff6bb3e..b98857117 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 b27f5c142..e0e769d48 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 64866ce9a..9eb2966d9 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,