diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs index a42cbb074..d496de67c 100644 --- a/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/Ryujinx.Graphics.GAL/Capabilities.cs @@ -5,26 +5,28 @@ namespace Ryujinx.Graphics.GAL public bool SupportsAstcCompression { get; } public bool SupportsImageLoadFormatted { get; } public bool SupportsNonConstantTextureOffset { get; } + public bool SupportsViewportSwizzle { get; } - public int MaximumComputeSharedMemorySize { get; } - public int StorageBufferOffsetAlignment { get; } - - public float MaxSupportedAnisotropy { get; } + public int MaximumComputeSharedMemorySize { get; } + public float MaximumSupportedAnisotropy { get; } + public int StorageBufferOffsetAlignment { get; } public Capabilities( bool supportsAstcCompression, bool supportsImageLoadFormatted, bool supportsNonConstantTextureOffset, + bool supportsViewportSwizzle, int maximumComputeSharedMemorySize, - int storageBufferOffsetAlignment, - float maxSupportedAnisotropy) + float maximumSupportedAnisotropy, + int storageBufferOffsetAlignment) { SupportsAstcCompression = supportsAstcCompression; SupportsImageLoadFormatted = supportsImageLoadFormatted; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; + SupportsViewportSwizzle = supportsViewportSwizzle; MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; + MaximumSupportedAnisotropy = maximumSupportedAnisotropy; StorageBufferOffsetAlignment = storageBufferOffsetAlignment; - MaxSupportedAnisotropy = maxSupportedAnisotropy; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs index 22e4e9e21..aa59713d9 100644 --- a/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/Ryujinx.Graphics.GAL/IPipeline.cs @@ -42,6 +42,8 @@ namespace Ryujinx.Graphics.GAL void SetImage(int index, ShaderStage stage, ITexture texture); + void SetOrigin(Origin origin); + void SetPointSize(float size); void SetPrimitiveRestart(bool enable, int index); diff --git a/Ryujinx.Graphics.GAL/Origin.cs b/Ryujinx.Graphics.GAL/Origin.cs new file mode 100644 index 000000000..d1b69cfd3 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Origin.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum Origin + { + UpperLeft, + LowerLeft + } +} diff --git a/Ryujinx.Graphics.GAL/ViewportSwizzle.cs b/Ryujinx.Graphics.GAL/ViewportSwizzle.cs index 5f04bf87d..c24a22464 100644 --- a/Ryujinx.Graphics.GAL/ViewportSwizzle.cs +++ b/Ryujinx.Graphics.GAL/ViewportSwizzle.cs @@ -2,13 +2,15 @@ namespace Ryujinx.Graphics.GAL { public enum ViewportSwizzle { - PositiveX, - NegativeX, - PositiveY, - NegativeY, - PositiveZ, - NegativeZ, - PositiveW, - NegativeW + PositiveX = 0, + NegativeX = 1, + PositiveY = 2, + NegativeY = 3, + PositiveZ = 4, + NegativeZ = 5, + PositiveW = 6, + NegativeW = 7, + + NegativeFlag = 1 } } diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index 733f15c12..d67ce2d11 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -422,10 +422,23 @@ namespace Ryujinx.Graphics.Gpu.Engine _context.Renderer.Pipeline.SetDepthMode(depthMode); - bool flipY = (state.Get(MethodOffset.YControl) & YControl.NegateY) != 0; - float yFlip = flipY ? -1 : 1; + YControl yControl = state.Get(MethodOffset.YControl); - Viewport[] viewports = new Viewport[Constants.TotalViewports]; + bool flipY = yControl.HasFlag(YControl.NegateY); + Origin origin = yControl.HasFlag(YControl.TriangleRastFlip) ? Origin.LowerLeft : Origin.UpperLeft; + + _context.Renderer.Pipeline.SetOrigin(origin); + + // The triangle rast flip flag only affects rasterization, the viewport is not flipped. + // Setting the origin mode to upper left on the host, however, not onlyy affects rasterization, + // but also flips the viewport. + // We negate the effects of flipping the viewport by flipping it again using the viewport swizzle. + if (origin == Origin.UpperLeft) + { + flipY = !flipY; + } + + Span viewports = stackalloc Viewport[Constants.TotalViewports]; for (int index = 0; index < Constants.TotalViewports; index++) { @@ -435,17 +448,42 @@ namespace Ryujinx.Graphics.Gpu.Engine float x = transform.TranslateX - MathF.Abs(transform.ScaleX); float y = transform.TranslateY - MathF.Abs(transform.ScaleY); - float width = transform.ScaleX * 2; - float height = transform.ScaleY * 2 * yFlip; + float width = MathF.Abs(transform.ScaleX) * 2; + float height = MathF.Abs(transform.ScaleY) * 2; RectangleF region = new RectangleF(x, y, width, height); + ViewportSwizzle swizzleX = transform.UnpackSwizzleX(); + ViewportSwizzle swizzleY = transform.UnpackSwizzleY(); + ViewportSwizzle swizzleZ = transform.UnpackSwizzleZ(); + ViewportSwizzle swizzleW = transform.UnpackSwizzleW(); + + if (transform.ScaleX < 0) + { + swizzleX ^= ViewportSwizzle.NegativeFlag; + } + + if (flipY) + { + swizzleY ^= ViewportSwizzle.NegativeFlag; + } + + if (transform.ScaleY < 0) + { + swizzleY ^= ViewportSwizzle.NegativeFlag; + } + + if (transform.ScaleZ < 0) + { + swizzleZ ^= ViewportSwizzle.NegativeFlag; + } + viewports[index] = new Viewport( region, - transform.UnpackSwizzleX(), - transform.UnpackSwizzleY(), - transform.UnpackSwizzleZ(), - transform.UnpackSwizzleW(), + swizzleX, + swizzleY, + swizzleZ, + swizzleW, extents.DepthNear, extents.DepthFar); } diff --git a/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/Ryujinx.Graphics.Gpu/Image/Sampler.cs index 827d6077f..92c255e5a 100644 --- a/Ryujinx.Graphics.Gpu/Image/Sampler.cs +++ b/Ryujinx.Graphics.Gpu/Image/Sampler.cs @@ -41,7 +41,7 @@ namespace Ryujinx.Graphics.Gpu.Image float mipLodBias = descriptor.UnpackMipLodBias(); float maxRequestedAnisotropy = GraphicsConfig.MaxAnisotropy >= 0 && GraphicsConfig.MaxAnisotropy <= 16 ? GraphicsConfig.MaxAnisotropy : descriptor.UnpackMaxAnisotropy(); - float maxSupportedAnisotropy = context.Capabilities.MaxSupportedAnisotropy; + float maxSupportedAnisotropy = context.Capabilities.MaximumSupportedAnisotropy; if (maxRequestedAnisotropy > maxSupportedAnisotropy) maxRequestedAnisotropy = maxSupportedAnisotropy; diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 76b06ea56..2774b7d12 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Shader; +using System; namespace Ryujinx.Graphics.Gpu.Shader { @@ -187,6 +188,12 @@ namespace Ryujinx.Graphics.Gpu.Shader /// True if the GPU and driver supports non-constant texture offsets, false otherwise public bool QuerySupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset; + /// + /// Queries host GPU viewport swizzle support. + /// + /// True if the GPU and driver supports viewport swizzle, false otherwise + public bool QuerySupportsViewportSwizzle() => _context.Capabilities.SupportsViewportSwizzle; + /// /// Queries texture format information, for shaders using image load or store. /// @@ -250,6 +257,24 @@ namespace Ryujinx.Graphics.Gpu.Shader }; } + public int QueryViewportSwizzle(int component) + { + YControl yControl = _state.Get(MethodOffset.YControl); + + bool flipY = yControl.HasFlag(YControl.NegateY) ^ !yControl.HasFlag(YControl.TriangleRastFlip); + + ViewportTransform transform = _state.Get(MethodOffset.ViewportTransform, 0); + + return component switch + { + 0 => (int)(transform.UnpackSwizzleX() ^ (transform.ScaleX < 0 ? ViewportSwizzle.NegativeFlag : 0)), + 1 => (int)(transform.UnpackSwizzleY() ^ (transform.ScaleY < 0 ? ViewportSwizzle.NegativeFlag : 0) ^ (flipY ? ViewportSwizzle.NegativeFlag : 0)), + 2 => (int)(transform.UnpackSwizzleZ() ^ (transform.ScaleZ < 0 ? ViewportSwizzle.NegativeFlag : 0)), + 3 => (int)transform.UnpackSwizzleW(), + _ => throw new ArgumentOutOfRangeException(nameof(component)) + }; + } + /// /// Gets the texture descriptor for a given texture on the pool. /// diff --git a/Ryujinx.Graphics.Gpu/State/GpuState.cs b/Ryujinx.Graphics.Gpu/State/GpuState.cs index 70c4ed614..264719b44 100644 --- a/Ryujinx.Graphics.Gpu/State/GpuState.cs +++ b/Ryujinx.Graphics.Gpu/State/GpuState.cs @@ -146,6 +146,9 @@ namespace Ryujinx.Graphics.Gpu.State { memory[(int)MethodOffset.ViewportExtents + index * 4 + 2] = 0; memory[(int)MethodOffset.ViewportExtents + index * 4 + 3] = 0x3F800000; + + // Set swizzle to +XYZW + memory[(int)MethodOffset.ViewportTransform + index * 8 + 6] = 0x6420; } // Viewport transform enable. diff --git a/Ryujinx.Graphics.OpenGL/EnumConversion.cs b/Ryujinx.Graphics.OpenGL/EnumConversion.cs index 014ddfc1d..aebe54fa5 100644 --- a/Ryujinx.Graphics.OpenGL/EnumConversion.cs +++ b/Ryujinx.Graphics.OpenGL/EnumConversion.cs @@ -416,5 +416,32 @@ namespace Ryujinx.Graphics.OpenGL return TextureTarget.Texture2D; } + + public static NvViewportSwizzle Convert(this ViewportSwizzle swizzle) + { + switch (swizzle) + { + case ViewportSwizzle.PositiveX: + return NvViewportSwizzle.ViewportSwizzlePositiveXNv; + case ViewportSwizzle.PositiveY: + return NvViewportSwizzle.ViewportSwizzlePositiveYNv; + case ViewportSwizzle.PositiveZ: + return NvViewportSwizzle.ViewportSwizzlePositiveZNv; + case ViewportSwizzle.PositiveW: + return NvViewportSwizzle.ViewportSwizzlePositiveWNv; + case ViewportSwizzle.NegativeX: + return NvViewportSwizzle.ViewportSwizzleNegativeXNv; + case ViewportSwizzle.NegativeY: + return NvViewportSwizzle.ViewportSwizzleNegativeYNv; + case ViewportSwizzle.NegativeZ: + return NvViewportSwizzle.ViewportSwizzleNegativeZNv; + case ViewportSwizzle.NegativeW: + return NvViewportSwizzle.ViewportSwizzleNegativeWNv; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(ViewportSwizzle)} enum value: {swizzle}."); + + return NvViewportSwizzle.ViewportSwizzlePositiveXNv; + } } } diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs index 229b35613..14515b618 100644 --- a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs +++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.OpenGL { private static readonly Lazy _supportsAstcCompression = new Lazy(() => HasExtension("GL_KHR_texture_compression_astc_ldr")); private static readonly Lazy _supportsImageLoadFormatted = new Lazy(() => HasExtension("GL_EXT_shader_image_load_formatted")); + private static readonly Lazy _supportsViewportSwizzle = new Lazy(() => HasExtension("GL_NV_viewport_swizzle")); private static readonly Lazy _maximumComputeSharedMemorySize = new Lazy(() => GetLimit(All.MaxComputeSharedMemorySize)); private static readonly Lazy _storageBufferOffsetAlignment = new Lazy(() => GetLimit(All.ShaderStorageBufferOffsetAlignment)); @@ -27,12 +28,13 @@ namespace Ryujinx.Graphics.OpenGL public static bool SupportsAstcCompression => _supportsAstcCompression.Value; public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value; + public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value; public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia; public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value; public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value; - public static float MaxSupportedAnisotropy => _maxSupportedAnisotropy.Value; + public static float MaximumSupportedAnisotropy => _maxSupportedAnisotropy.Value; private static bool HasExtension(string name) { diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs index ee4ce298b..6c511e093 100644 --- a/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -650,6 +650,13 @@ namespace Ryujinx.Graphics.OpenGL _vertexArray.SetIndexBuffer(buffer.Handle); } + public void SetOrigin(Origin origin) + { + ClipOrigin clipOrigin = origin == Origin.UpperLeft ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft; + + SetOrigin(clipOrigin); + } + public void SetPointSize(float size) { GL.PointSize(size); @@ -854,8 +861,6 @@ namespace Ryujinx.Graphics.OpenGL public void SetViewports(int first, ReadOnlySpan viewports) { - bool flipY = false; - float[] viewportArray = new float[viewports.Length * 4]; double[] depthRangeArray = new double[viewports.Length * 2]; @@ -869,17 +874,14 @@ namespace Ryujinx.Graphics.OpenGL viewportArray[viewportElemIndex + 0] = viewport.Region.X; viewportArray[viewportElemIndex + 1] = viewport.Region.Y; - // OpenGL does not support per-viewport flipping, so - // instead we decide that based on the viewport 0 value. - // It will apply to all viewports. - if (index == 0) + if (HwCapabilities.SupportsViewportSwizzle) { - flipY = viewport.Region.Height < 0; - } - - if (viewport.SwizzleY == ViewportSwizzle.NegativeY) - { - flipY = !flipY; + GL.NV.ViewportSwizzle( + index, + viewport.SwizzleX.Convert(), + viewport.SwizzleY.Convert(), + viewport.SwizzleZ.Convert(), + viewport.SwizzleW.Convert()); } viewportArray[viewportElemIndex + 2] = MathF.Abs(viewport.Region.Width); @@ -892,8 +894,6 @@ namespace Ryujinx.Graphics.OpenGL GL.ViewportArray(first, viewports.Length, viewportArray); GL.DepthRangeArray(first, viewports.Length, depthRangeArray); - - SetOrigin(flipY ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft); } public void TextureBarrier() diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index c839c2967..49dba9cc9 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -75,9 +75,10 @@ namespace Ryujinx.Graphics.OpenGL HwCapabilities.SupportsAstcCompression, HwCapabilities.SupportsImageLoadFormatted, HwCapabilities.SupportsNonConstantTextureOffset, + HwCapabilities.SupportsViewportSwizzle, HwCapabilities.MaximumComputeSharedMemorySize, - HwCapabilities.StorageBufferOffsetAlignment, - HwCapabilities.MaxSupportedAnisotropy); + HwCapabilities.MaximumSupportedAnisotropy, + HwCapabilities.StorageBufferOffsetAlignment); } public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs index e10d869bf..06333dac0 100644 --- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -64,9 +64,22 @@ return true; } + public bool QuerySupportsViewportSwizzle() + { + return true; + } + public TextureFormat QueryTextureFormat(int handle) { return TextureFormat.R8G8B8A8Unorm; } + + public int QueryViewportSwizzle(int component) + { + // Bit 0: Negate flag. + // Bits 2-1: Component. + // Example: 0b110 = W, 0b111 = -W, 0b000 = X, 0b010 = Y etc. + return component << 1; + } } } diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index 8044f074a..70476dcd1 100644 --- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -11,9 +11,7 @@ namespace Ryujinx.Graphics.Shader.Translation public Block CurrBlock { get; set; } public OpCode CurrOp { get; set; } - private ShaderConfig _config; - - public ShaderConfig Config => _config; + public ShaderConfig Config { get; } private List _operations; @@ -21,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.Translation public EmitterContext(ShaderConfig config) { - _config = config; + Config = config; _operations = new List(); @@ -61,13 +59,40 @@ namespace Ryujinx.Graphics.Shader.Translation public void PrepareForReturn() { - if (_config.Stage == ShaderStage.Fragment) + if (Config.Stage == ShaderStage.Vertex && (Config.Flags & TranslationFlags.VertexA) == 0) { - if (_config.OmapDepth) + // Here we attempt to implement viewport swizzle on the vertex shader. + // Perform permutation and negation of the output gl_Position components. + // Note that per-viewport swizzling can't be supported using this approach. + int swizzleX = Config.GpuAccessor.QueryViewportSwizzle(0); + int swizzleY = Config.GpuAccessor.QueryViewportSwizzle(1); + int swizzleZ = Config.GpuAccessor.QueryViewportSwizzle(2); + int swizzleW = Config.GpuAccessor.QueryViewportSwizzle(3); + + bool nonStandardSwizzle = swizzleX != 0 || swizzleY != 2 || swizzleZ != 4 || swizzleW != 6; + + if (!Config.GpuAccessor.QuerySupportsViewportSwizzle() && nonStandardSwizzle) + { + Operand[] temp = new Operand[4]; + + temp[0] = this.Copy(Attribute(AttributeConsts.PositionX)); + temp[1] = this.Copy(Attribute(AttributeConsts.PositionY)); + temp[2] = this.Copy(Attribute(AttributeConsts.PositionZ)); + temp[3] = this.Copy(Attribute(AttributeConsts.PositionW)); + + this.Copy(Attribute(AttributeConsts.PositionX), this.FPNegate(temp[(swizzleX >> 1) & 3], (swizzleX & 1) != 0)); + this.Copy(Attribute(AttributeConsts.PositionY), this.FPNegate(temp[(swizzleY >> 1) & 3], (swizzleY & 1) != 0)); + this.Copy(Attribute(AttributeConsts.PositionZ), this.FPNegate(temp[(swizzleZ >> 1) & 3], (swizzleZ & 1) != 0)); + this.Copy(Attribute(AttributeConsts.PositionW), this.FPNegate(temp[(swizzleW >> 1) & 3], (swizzleW & 1) != 0)); + } + } + else if (Config.Stage == ShaderStage.Fragment) + { + if (Config.OmapDepth) { Operand dest = Attribute(AttributeConsts.FragmentOutputDepth); - Operand src = Register(_config.GetDepthRegister(), RegisterType.Gpr); + Operand src = Register(Config.GetDepthRegister(), RegisterType.Gpr); this.Copy(dest, src); } @@ -76,7 +101,7 @@ namespace Ryujinx.Graphics.Shader.Translation for (int attachment = 0; attachment < 8; attachment++) { - OmapTarget target = _config.OmapTargets[attachment]; + OmapTarget target = Config.OmapTargets[attachment]; for (int component = 0; component < 4; component++) { diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs index b87157462..1874dec3b 100644 --- a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs +++ b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs @@ -7,7 +7,8 @@ namespace Ryujinx.Graphics.Shader.Translation { None = 0, - Compute = 1 << 0, - DebugMode = 1 << 1 + VertexA = 1 << 0, + Compute = 1 << 1, + DebugMode = 1 << 2 } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs index fb6935afc..1a69c5114 100644 --- a/Ryujinx.Graphics.Shader/Translation/Translator.cs +++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation public static ShaderProgram Translate(ulong addressA, ulong addressB, IGpuAccessor gpuAccessor, TranslationFlags flags) { - Operation[] opsA = DecodeShader(addressA, gpuAccessor, flags, out _, out int sizeA); + Operation[] opsA = DecodeShader(addressA, gpuAccessor, flags | TranslationFlags.VertexA, out _, out int sizeA); Operation[] opsB = DecodeShader(addressB, gpuAccessor, flags, out ShaderConfig config, out int sizeB); return Translate(Combine(opsA, opsB), config, sizeB, sizeA);