From edba47b5296a3c14a1f52beccc8e1cf39f2ca95d Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 3 Mar 2019 15:11:27 -0600 Subject: [PATCH] Apply #595 --- Ryujinx.Graphics/Gal/IGalPipeline.cs | 1 + Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs | 36 +++++- Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs | 85 +++++++------ .../Gal/OpenGL/OGLRenderTarget.cs | 3 - Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs | 119 +++++++++++++----- Ryujinx.Graphics/NvGpu.cs | 2 + 6 files changed, 172 insertions(+), 74 deletions(-) diff --git a/Ryujinx.Graphics/Gal/IGalPipeline.cs b/Ryujinx.Graphics/Gal/IGalPipeline.cs index 4f14968350..1ecb260288 100644 --- a/Ryujinx.Graphics/Gal/IGalPipeline.cs +++ b/Ryujinx.Graphics/Gal/IGalPipeline.cs @@ -3,6 +3,7 @@ public interface IGalPipeline { void Bind(GalPipelineState state); + void Unbind(GalPipelineState state); void ResetDepthMask(); void ResetColorMask(int index); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs index 0cfe62c1a3..8a1a0510f7 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs @@ -1,19 +1,23 @@ using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; using System; namespace Ryujinx.Graphics.Gal.OpenGL { static class OglExtension { + // Private lazy backing variables private static Lazy _enhancedLayouts = new Lazy(() => HasExtension("GL_ARB_enhanced_layouts")); private static Lazy _textureMirrorClamp = new Lazy(() => HasExtension("GL_EXT_texture_mirror_clamp")); private static Lazy _viewportArray = new Lazy(() => HasExtension("GL_ARB_viewport_array")); private static Lazy _nvidiaDriver = new Lazy(() => IsNvidiaDriver()); + // Public accessors public static bool EnhancedLayouts => _enhancedLayouts.Value; public static bool TextureMirrorClamp => _textureMirrorClamp.Value; public static bool ViewportArray => _viewportArray.Value; + public static bool NvidiaDriver => _nvidiaDriver.Value; private static bool HasExtension(string name) @@ -28,11 +32,39 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } + Logger.PrintInfo(LogClass.Gpu, $"OpenGL extension {name} unavailable. You may experience some performance degradation"); + return false; } - private static bool IsNvidiaDriver() { + private static bool IsNvidiaDriver() + { return GL.GetString(StringName.Vendor).Equals("NVIDIA Corporation"); } + + public static class Required + { + // Public accessors + public static bool EnhancedLayouts => _enhancedLayoutsRequired.Value; + public static bool TextureMirrorClamp => _textureMirrorClampRequired.Value; + public static bool ViewportArray => _viewportArrayRequired.Value; + + // Private lazy backing variables + private static Lazy _enhancedLayoutsRequired = new Lazy(() => HasExtensionRequired(OglExtension.EnhancedLayouts, "GL_ARB_enhanced_layouts")); + private static Lazy _textureMirrorClampRequired = new Lazy(() => HasExtensionRequired(OglExtension.TextureMirrorClamp, "GL_EXT_texture_mirror_clamp")); + private static Lazy _viewportArrayRequired = new Lazy(() => HasExtensionRequired(OglExtension.ViewportArray, "GL_ARB_viewport_array")); + + private static bool HasExtensionRequired(bool value, string name) + { + if (value) + { + return true; + } + + Logger.PrintWarning(LogClass.Gpu, $"Required OpenGL extension {name} unavailable. You may experience some rendering issues"); + + return false; + } + } } -} \ No newline at end of file +} diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs index 0792835a91..3c8ada3ea6 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs @@ -270,47 +270,52 @@ namespace Ryujinx.Graphics.Gal.OpenGL // Scissor Test - bool forceUpdate; - - for (int index = 0; index < New.ScissorTestCount; index++) + // All scissor test are disabled before drawing final framebuffer to screen so we don't need to handle disabling + // Skip if there are no scissor tests to enable + if (New.ScissorTestCount != 0) { - forceUpdate = false; + int scissorsApplied = 0; + bool applyToAll = false; - if (New.ScissorTestEnabled[index]) + for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) { - // If there is only 1 scissor test, geometry shaders are disabled so the scissor test applies to all viewports - if (New.ScissorTestCount == 1) + if (New.ScissorTestEnabled[index]) { - GL.Enable(EnableCap.ScissorTest); - } - else - { - GL.Enable(IndexedEnableCap.ScissorTest, index); - } - forceUpdate = true; - } - else - { - GL.Disable(IndexedEnableCap.ScissorTest, index); - } + // If viewport arrays are unavailable apply first scissor test to all or + // there is only 1 scissor test and it's the first, the scissor test applies to all viewports + if (!OglExtension.Required.ViewportArray || (index == 0 && New.ScissorTestCount == 1)) + { + GL.Enable(EnableCap.ScissorTest); + applyToAll = true; + } + else + { + GL.Enable(IndexedEnableCap.ScissorTest, index); + } - if (New.ScissorTestEnabled[index] && - (New.ScissorTestX[index] != _old.ScissorTestX[index] || - New.ScissorTestY[index] != _old.ScissorTestY[index] || - New.ScissorTestWidth[index] != _old.ScissorTestWidth[index] || - New.ScissorTestHeight[index] != _old.ScissorTestHeight[index] || - forceUpdate)) // Force update intentionally last to reduce if comparisons - { - // If there is only 1 scissor test geometry shaders are disables so the scissor test applies to all viewports - if (New.ScissorTestCount == 1) - { - GL.Scissor(New.ScissorTestX[index], New.ScissorTestY[index], - New.ScissorTestWidth[index], New.ScissorTestHeight[index]); - } - else - { - GL.ScissorIndexed(index, New.ScissorTestX[index], New.ScissorTestY[index], - New.ScissorTestWidth[index], New.ScissorTestHeight[index]); + if (New.ScissorTestEnabled[index] != _old.ScissorTestEnabled[index] || + New.ScissorTestX[index] != _old.ScissorTestX[index] || + New.ScissorTestY[index] != _old.ScissorTestY[index] || + New.ScissorTestWidth[index] != _old.ScissorTestWidth[index] || + New.ScissorTestHeight[index] != _old.ScissorTestHeight[index]) + { + if (applyToAll) + { + GL.Scissor(New.ScissorTestX[index], New.ScissorTestY[index], + New.ScissorTestWidth[index], New.ScissorTestHeight[index]); + } + else + { + GL.ScissorIndexed(index, New.ScissorTestX[index], New.ScissorTestY[index], + New.ScissorTestWidth[index], New.ScissorTestHeight[index]); + } + } + + // If all scissor tests have been applied, or viewport arrays are unavailable we can skip remaining iterations + if (!OglExtension.Required.ViewportArray || ++scissorsApplied == New.ScissorTestCount) + { + break; + } } } } @@ -378,6 +383,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL _old = New; } + public void Unbind(GalPipelineState state) + { + if (state.ScissorTestCount > 0) + { + GL.Disable(EnableCap.ScissorTest); + } + } + private void SetAllBlendState(BlendState New) { Enable(EnableCap.Blend, New.Enabled); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs index 5abbeca198..d36bac1bcf 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs @@ -367,9 +367,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.Disable(EnableCap.FramebufferSrgb); - // Will be re-enabled if needed while binding, called before any game GL calls - GL.Disable(EnableCap.ScissorTest); - GL.BlitFramebuffer( srcX0, srcY0, diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs index ab2822781f..b47825218c 100644 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs @@ -24,7 +24,12 @@ namespace Ryujinx.Graphics.Graphics3d private ConstBuffer[][] _constBuffers; - // Height kept for flipping y axis + // Viewport dimensions kept for scissor test limits + private int _viewportX0 = 0; + private int _viewportY0 = 0; + private int _viewportX1 = 0; + private int _viewportY1 = 0; + private int _viewportWidth = 0; private int _viewportHeight = 0; private int _currentInstance = 0; @@ -97,7 +102,14 @@ namespace Ryujinx.Graphics.Graphics3d GalPipelineState state = new GalPipelineState(); + // Framebuffer must be run configured because viewport dimensions may be used in other methods SetFrameBuffer(state); + + for (int fbIndex = 0; fbIndex < 8; fbIndex++) + { + SetFrameBuffer(vmm, fbIndex); + } + SetFrontFace(state); SetCullFace(state); SetDepth(state); @@ -107,11 +119,6 @@ namespace Ryujinx.Graphics.Graphics3d SetColorMask(state); SetPrimitiveRestart(state); - for (int fbIndex = 0; fbIndex < 8; fbIndex++) - { - SetFrameBuffer(vmm, fbIndex); - } - SetZeta(vmm); SetRenderTargets(); @@ -206,11 +213,11 @@ namespace Ryujinx.Graphics.Graphics3d float sx = ReadRegisterFloat(NvGpuEngine3DReg.ViewportNScaleX + fbIndex * 8); float sy = ReadRegisterFloat(NvGpuEngine3DReg.ViewportNScaleY + fbIndex * 8); - int vpX = (int)MathF.Max(0, tx - MathF.Abs(sx)); - int vpY = (int)MathF.Max(0, ty - MathF.Abs(sy)); + _viewportX0 = (int)MathF.Max(0, tx - MathF.Abs(sx)); + _viewportY0 = (int)MathF.Max(0, ty - MathF.Abs(sy)); - int vpW = (int)(tx + MathF.Abs(sx)) - vpX; - int vpH = (int)(ty + MathF.Abs(sy)) - vpY; + _viewportX1 = (int)(tx + MathF.Abs(sx)); + _viewportY1 = (int)(ty + MathF.Abs(sy)); GalImageFormat format = ImageUtils.ConvertSurface((GalSurfaceFormat)surfFormat); @@ -218,9 +225,7 @@ namespace Ryujinx.Graphics.Graphics3d _gpu.ResourceManager.SendColorBuffer(vmm, key, fbIndex, image); - _viewportHeight = vpH; - - _gpu.Renderer.RenderTarget.SetViewport(fbIndex, vpX, vpY, vpW, vpH); + _gpu.Renderer.RenderTarget.SetViewport(fbIndex, _viewportX0, _viewportY0, _viewportX1 - _viewportX0, _viewportY1 - _viewportY0); } private void SetFrameBuffer(GalPipelineState state) @@ -422,38 +427,83 @@ namespace Ryujinx.Graphics.Graphics3d private void SetScissor(GalPipelineState state) { - // FIXME: Stubbed, only the first scissor test is valid without a geometry shader loaded. At time of writing geometry shaders are also stubbed. - // Once geometry shaders are fixed it should be equal to GalPipelineState.RenderTargetCount when shader loaded, otherwise equal to 1 - state.ScissorTestCount = 1; + int count = 0; - for (int index = 0; index < state.ScissorTestCount; index++) + for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) { state.ScissorTestEnabled[index] = ReadRegisterBool(NvGpuEngine3DReg.ScissorEnable + index * 4); if (state.ScissorTestEnabled[index]) { - uint scissorHorizontal = (uint)ReadRegister(NvGpuEngine3DReg.ScissorHorizontal + index * 4); - uint scissorVertical = (uint)ReadRegister(NvGpuEngine3DReg.ScissorVertical + index * 4); + uint scissorHorizontal = (uint)ReadRegister(NvGpuEngine3DReg.ScissorHorizontal + index * 4); + uint scissorVertical = (uint)ReadRegister(NvGpuEngine3DReg.ScissorVertical + index * 4); - state.ScissorTestX[index] = (int)((scissorHorizontal & 0xFFFF) * state.FlipX); // X, lower 16 bits - state.ScissorTestWidth[index] = (int)((scissorHorizontal >> 16) * state.FlipX) - state.ScissorTestX[index]; // Width, right side is upper 16 bits + int left = (int)(scissorHorizontal & 0xFFFF); // Left, lower 16 bits + int right = (int)(scissorHorizontal >> 16); // Right, upper 16 bits - state.ScissorTestY[index] = (int)((scissorVertical & 0xFFFF)); // Y, lower 16 bits - state.ScissorTestHeight[index] = (int)((scissorVertical >> 16)) - state.ScissorTestY[index]; // Height, top side is upper 16 bits + int bottom = (int)(scissorVertical & 0xFFFF); // Bottom, lower 16 bits + int top = (int)(scissorVertical >> 16); // Top, upper 16 bits - // Y coordinates may have to be flipped - if ((int)state.FlipY == -1) + int width = Math.Abs(right - left); + int height = Math.Abs(top - bottom); + + // If the scissor test covers the whole possible viewport, i.e. uninitialized, disable scissor test + if ((width > NvGpu.MaxViewportSize && height > NvGpu.MaxViewportSize) || width <= 0 || height <= 0) { - state.ScissorTestY[index] = _viewportHeight - state.ScissorTestY[index] - state.ScissorTestHeight[index]; - - // Handle negative viewpont coordinate - if (state.ScissorTestY[index] < 0) - { - state.ScissorTestY[index] = 0; - } + state.ScissorTestEnabled[index] = false; + continue; } + + // Keep track of how many scissor tests are active. + // If only 1, and it's the first user should apply to all viewports + count++; + + // Flip X + if (state.FlipX == -1) + { + left = _viewportX1 - (left - _viewportX0); + right = _viewportX1 - (right - _viewportX0); + } + + // Ensure X is in the right order + if (left > right) + { + int temp = left; + left = right; + right = temp; + } + + // Flip Y + if (state.FlipY == -1) + { + bottom = _viewportY1 - (bottom - _viewportY0); + top = _viewportY1 - (top - _viewportY0); + } + + // Ensure Y is in the right order + if (bottom > top) + { + int temp = top; + top = bottom; + bottom = temp; + } + + // Handle out of active viewport dimensions + left = Math.Clamp(left, _viewportX0, _viewportX1); + right = Math.Clamp(right, _viewportX0, _viewportX1); + top = Math.Clamp(top, _viewportY0, _viewportY1); + bottom = Math.Clamp(bottom, _viewportY0, _viewportY1); + + // Save values to state + state.ScissorTestX[index] = left; + state.ScissorTestY[index] = bottom; + + state.ScissorTestWidth[index] = right - left; + state.ScissorTestHeight[index] = top - bottom; } } + + state.ScissorTestCount = count; } private void SetBlending(GalPipelineState state) @@ -541,7 +591,7 @@ namespace Ryujinx.Graphics.Graphics3d private void SetRenderTargets() { //Commercial games do not seem to - //bool SeparateFragData = ReadRegisterBool(NvGpuEngine3dReg.RTSeparateFragData); + //bool SeparateFragData = ReadRegisterBool(NvGpuEngine3DReg.RTSeparateFragData); uint control = (uint)(ReadRegister(NvGpuEngine3DReg.RtControl)); @@ -996,6 +1046,9 @@ namespace Ryujinx.Graphics.Graphics3d _gpu.Renderer.Rasterizer.DrawArrays(vertexFirst, vertexCount, primType); } + // Reset pipeline for host OpenGL calls + _gpu.Renderer.Pipeline.Unbind(state); + //Is the GPU really clearing those registers after draw? WriteRegister(NvGpuEngine3DReg.IndexBatchFirst, 0); WriteRegister(NvGpuEngine3DReg.IndexBatchCount, 0); diff --git a/Ryujinx.Graphics/NvGpu.cs b/Ryujinx.Graphics/NvGpu.cs index e71d96e1d4..41a83323e8 100644 --- a/Ryujinx.Graphics/NvGpu.cs +++ b/Ryujinx.Graphics/NvGpu.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Graphics { public class NvGpu { + public const int MaxViewportSize = 0x3FFF; + public IGalRenderer Renderer { get; private set; } public GpuResourceManager ResourceManager { get; private set; }