From eca8808649b7d763dd6b528bd26d43b7bd839cd2 Mon Sep 17 00:00:00 2001 From: Theun de Bruijn Date: Tue, 26 Sep 2023 07:40:16 +1000 Subject: [PATCH] Headless: Add support for Scaling Filters, Anti-aliasing and Exclusive Fullscreen (#5412) * Headless: Added support for fullscreen option * Headless: cleanup of fullscreen support * Headless: fullscreen support : implemented proposed changes * Headless: fullscreen support: cleanup * Headless: exclusive fullscreen support: wip * Headless: exclusive fullscreen support: add. windows scale interop * Headless: exclusive fullscreen support: cleanup * Headless: exclusive fullscreen support: cleanup * Headless: fullscreen support: fix for OpenGL scaling * Headless: fullscreen support: cleanup * Headless: fullscreen support: cleanup * Headless: fullscreen support: add. Vulkan fix * Headless: fullscreen support: add. macOS fullscreen fix * Headless: fullscreen support: cleanup * Headless: fullscreen support: cleanup * Headless: fullscreen support: cleanup * Headless: exclusive fullscreen support: add. display selection logic * Headless: exclusive fullscreen support: add. anti-aliasing + scaling-filter logic * Headless: exclusive fullscreen support: upd. options to be case-insensitive * Headless: exclusive fullscreen support: force default values for scaling + anti-aliasing options * Headless: upd. OpenGL --fullscreen window size logic * Headless: upd. fullscreen logic * Headless: cleanup * Headless: refac. DisplayId option naming * Headless: refac. scaling + anti-aliasing option handling * Headless: refac. namespace handling * Headless: upd. imports ordering * Apply suggestions from code review Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> --------- Co-authored-by: Ac_K Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> --- .../OpenGL/OpenGLWindow.cs | 10 ++-- src/Ryujinx.Headless.SDL2/Options.cs | 23 +++++++- src/Ryujinx.Headless.SDL2/Program.cs | 7 +++ .../Vulkan/VulkanWindow.cs | 12 ++++- src/Ryujinx.Headless.SDL2/WindowBase.cs | 53 ++++++++++++++++--- 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs b/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs index a2b26bbc5..245aba778 100644 --- a/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs +++ b/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs @@ -151,11 +151,15 @@ namespace Ryujinx.Headless.SDL2.OpenGL GL.Clear(ClearBufferMask.ColorBufferBit); SwapBuffers(); - if (IsFullscreen) + if (IsExclusiveFullscreen) + { + Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + } + else if (IsFullscreen) { // NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow. - // we might have to amend this if people run this on a non-primary display set to a different resolution. - if (SDL_GetDisplayBounds(0, out SDL_Rect displayBounds) < 0) + if (SDL_GetDisplayBounds(DisplayId, out SDL_Rect displayBounds) < 0) { Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}"); diff --git a/src/Ryujinx.Headless.SDL2/Options.cs b/src/Ryujinx.Headless.SDL2/Options.cs index e44cedec9..a1adcd024 100644 --- a/src/Ryujinx.Headless.SDL2/Options.cs +++ b/src/Ryujinx.Headless.SDL2/Options.cs @@ -14,9 +14,21 @@ namespace Ryujinx.Headless.SDL2 [Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")] public string UserProfile { get; set; } - [Option("fullscreen", Required = false, HelpText = "Launch the game in fullscreen mode.")] + [Option("display-id", Required = false, Default = 0, HelpText = "Set the display to use - especially helpful for fullscreen mode. [0-n]")] + public int DisplayId { get; set; } + + [Option("fullscreen", Required = false, Default = false, HelpText = "Launch the game in fullscreen mode.")] public bool IsFullscreen { get; set; } + [Option("exclusive-fullscreen", Required = false, Default = false, HelpText = "Launch the game in exclusive fullscreen mode.")] + public bool IsExclusiveFullscreen { get; set; } + + [Option("exclusive-fullscreen-width", Required = false, Default = 1920, HelpText = "Set horizontal resolution for exclusive fullscreen mode.")] + public int ExclusiveFullscreenWidth { get; set; } + + [Option("exclusive-fullscreen-height", Required = false, Default = 1080, HelpText = "Set vertical resolution for exclusive fullscreen mode.")] + public int ExclusiveFullscreenHeight { get; set; } + // Input [Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")] @@ -196,6 +208,15 @@ namespace Ryujinx.Headless.SDL2 [Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")] public string PreferredGPUVendor { get; set; } + [Option("anti-aliasing", Required = false, Default = AntiAliasing.None, HelpText = "Set the type of anti aliasing being used. [None|Fxaa|SmaaLow|SmaaMedium|SmaaHigh|SmaaUltra]")] + public AntiAliasing AntiAliasing { get; set; } + + [Option("scaling-filter", Required = false, Default = ScalingFilter.Bilinear, HelpText = "Set the scaling filter. [Bilinear|Nearest|Fsr]")] + public ScalingFilter ScalingFilter { get; set; } + + [Option("scaling-filter-level", Required = false, Default = 0, HelpText = "Set the scaling filter intensity (currently only applies to FSR). [0-100]")] + public int ScalingFilterLevel { get; set; } + // Hacks [Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")] diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index 98cc5abf4..e90d5595e 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -596,6 +596,13 @@ namespace Ryujinx.Headless.SDL2 _window = window; _window.IsFullscreen = options.IsFullscreen; + _window.DisplayId = options.DisplayId; + _window.IsExclusiveFullscreen = options.IsExclusiveFullscreen; + _window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth; + _window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight; + _window.AntiAliasing = options.AntiAliasing; + _window.ScalingFilter = options.ScalingFilter; + _window.ScalingFilterLevel = options.ScalingFilterLevel; _emulationContext = InitializeEmulationContext(window, renderer, options); diff --git a/src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs b/src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs index f2f337a53..4a04b10a3 100644 --- a/src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs +++ b/src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs @@ -29,8 +29,16 @@ namespace Ryujinx.Headless.SDL2.Vulkan protected override void InitializeRenderer() { - Renderer?.Window.SetSize(DefaultWidth, DefaultHeight); - MouseDriver.SetClientSize(DefaultWidth, DefaultHeight); + if (IsExclusiveFullscreen) + { + Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + } + else + { + Renderer?.Window.SetSize(DefaultWidth, DefaultHeight); + MouseDriver.SetClientSize(DefaultWidth, DefaultHeight); + } } private static void BasicInvoke(Action action) diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs index 0b91da5c6..1b9556057 100644 --- a/src/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -20,6 +20,8 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading; using static SDL2.SDL; +using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; +using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; using Switch = Ryujinx.HLE.Switch; namespace Ryujinx.Headless.SDL2 @@ -28,8 +30,9 @@ namespace Ryujinx.Headless.SDL2 { protected const int DefaultWidth = 1280; protected const int DefaultHeight = 720; - private const SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN; private const int TargetFps = 60; + private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN; + private SDL_WindowFlags FullscreenFlag = 0; private static readonly ConcurrentQueue _mainThreadActions = new(); @@ -54,7 +57,14 @@ namespace Ryujinx.Headless.SDL2 public IHostUiTheme HostUiTheme { get; } public int Width { get; private set; } public int Height { get; private set; } + public int DisplayId { get; set; } public bool IsFullscreen { get; set; } + public bool IsExclusiveFullscreen { get; set; } + public int ExclusiveFullscreenWidth { get; set; } + public int ExclusiveFullscreenHeight { get; set; } + public AntiAliasing AntiAliasing { get; set; } + public ScalingFilter ScalingFilter { get; set; } + public int ScalingFilterLevel { get; set; } protected SDL2MouseDriver MouseDriver; private readonly InputManager _inputManager; @@ -158,9 +168,24 @@ namespace Ryujinx.Headless.SDL2 string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})"; string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; - SDL_WindowFlags fullscreenFlag = IsFullscreen ? SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0; + Width = DefaultWidth; + Height = DefaultHeight; - WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | fullscreenFlag | GetWindowFlags()); + if (IsExclusiveFullscreen) + { + Width = ExclusiveFullscreenWidth; + Height = ExclusiveFullscreenHeight; + + DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; + FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; + } + else if (IsFullscreen) + { + DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; + FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags()); if (WindowHandle == IntPtr.Zero) { @@ -175,9 +200,6 @@ namespace Ryujinx.Headless.SDL2 _windowId = SDL_GetWindowID(WindowHandle); SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent); - - Width = DefaultWidth; - Height = DefaultHeight; } private void HandleWindowEvent(SDL_Event evnt) @@ -189,8 +211,8 @@ namespace Ryujinx.Headless.SDL2 case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED: // Unlike on Windows, this event fires on macOS when triggering fullscreen mode. // And promptly crashes the process because `Renderer?.window.SetSize` is undefined. - // As we don't need this to fire in either case we can test for isFullscreen. - if (!IsFullscreen) + // As we don't need this to fire in either case we can test for fullscreen. + if (!IsFullscreen && !IsExclusiveFullscreen) { Width = evnt.window.data1; Height = evnt.window.data2; @@ -225,6 +247,17 @@ namespace Ryujinx.Headless.SDL2 return Renderer.GetHardwareInfo().GpuVendor; } + private void SetAntiAliasing() + { + Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)AntiAliasing); + } + + private void SetScalingFilter() + { + Renderer?.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ScalingFilter); + Renderer?.Window.SetScalingFilterLevel(ScalingFilterLevel); + } + public void Render() { InitializeWindowRenderer(); @@ -233,6 +266,10 @@ namespace Ryujinx.Headless.SDL2 InitializeRenderer(); + SetAntiAliasing(); + + SetScalingFilter(); + _gpuVendorName = GetGpuVendorName(); Device.Gpu.Renderer.RunLoop(() =>