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 <Acoustik666@gmail.com> Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
This commit is contained in:
parent
f6c3f1cdfd
commit
eca8808649
5 changed files with 91 additions and 14 deletions
|
@ -151,11 +151,15 @@ namespace Ryujinx.Headless.SDL2.OpenGL
|
||||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||||
SwapBuffers();
|
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.
|
// 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(DisplayId, out SDL_Rect displayBounds) < 0)
|
||||||
if (SDL_GetDisplayBounds(0, out SDL_Rect displayBounds) < 0)
|
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}");
|
Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}");
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,21 @@ namespace Ryujinx.Headless.SDL2
|
||||||
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
|
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
|
||||||
public string UserProfile { get; set; }
|
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; }
|
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
|
// Input
|
||||||
|
|
||||||
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
|
[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.")]
|
[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; }
|
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
|
// Hacks
|
||||||
|
|
||||||
[Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")]
|
[Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")]
|
||||||
|
|
|
@ -596,6 +596,13 @@ namespace Ryujinx.Headless.SDL2
|
||||||
_window = window;
|
_window = window;
|
||||||
|
|
||||||
_window.IsFullscreen = options.IsFullscreen;
|
_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);
|
_emulationContext = InitializeEmulationContext(window, renderer, options);
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,18 @@ namespace Ryujinx.Headless.SDL2.Vulkan
|
||||||
protected override void InitializeWindowRenderer() { }
|
protected override void InitializeWindowRenderer() { }
|
||||||
|
|
||||||
protected override void InitializeRenderer()
|
protected override void InitializeRenderer()
|
||||||
|
{
|
||||||
|
if (IsExclusiveFullscreen)
|
||||||
|
{
|
||||||
|
Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||||
|
MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
||||||
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void BasicInvoke(Action action)
|
private static void BasicInvoke(Action action)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,6 +20,8 @@ using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using static SDL2.SDL;
|
using static SDL2.SDL;
|
||||||
|
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||||
|
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
||||||
using Switch = Ryujinx.HLE.Switch;
|
using Switch = Ryujinx.HLE.Switch;
|
||||||
|
|
||||||
namespace Ryujinx.Headless.SDL2
|
namespace Ryujinx.Headless.SDL2
|
||||||
|
@ -28,8 +30,9 @@ namespace Ryujinx.Headless.SDL2
|
||||||
{
|
{
|
||||||
protected const int DefaultWidth = 1280;
|
protected const int DefaultWidth = 1280;
|
||||||
protected const int DefaultHeight = 720;
|
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 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<Action> _mainThreadActions = new();
|
private static readonly ConcurrentQueue<Action> _mainThreadActions = new();
|
||||||
|
|
||||||
|
@ -54,7 +57,14 @@ namespace Ryujinx.Headless.SDL2
|
||||||
public IHostUiTheme HostUiTheme { get; }
|
public IHostUiTheme HostUiTheme { get; }
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
|
public int DisplayId { get; set; }
|
||||||
public bool IsFullscreen { 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;
|
protected SDL2MouseDriver MouseDriver;
|
||||||
private readonly InputManager _inputManager;
|
private readonly InputManager _inputManager;
|
||||||
|
@ -158,9 +168,24 @@ namespace Ryujinx.Headless.SDL2
|
||||||
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
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)
|
if (WindowHandle == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
|
@ -175,9 +200,6 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
_windowId = SDL_GetWindowID(WindowHandle);
|
_windowId = SDL_GetWindowID(WindowHandle);
|
||||||
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
|
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
|
||||||
|
|
||||||
Width = DefaultWidth;
|
|
||||||
Height = DefaultHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleWindowEvent(SDL_Event evnt)
|
private void HandleWindowEvent(SDL_Event evnt)
|
||||||
|
@ -189,8 +211,8 @@ namespace Ryujinx.Headless.SDL2
|
||||||
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
// Unlike on Windows, this event fires on macOS when triggering fullscreen mode.
|
// Unlike on Windows, this event fires on macOS when triggering fullscreen mode.
|
||||||
// And promptly crashes the process because `Renderer?.window.SetSize` is undefined.
|
// 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.
|
// As we don't need this to fire in either case we can test for fullscreen.
|
||||||
if (!IsFullscreen)
|
if (!IsFullscreen && !IsExclusiveFullscreen)
|
||||||
{
|
{
|
||||||
Width = evnt.window.data1;
|
Width = evnt.window.data1;
|
||||||
Height = evnt.window.data2;
|
Height = evnt.window.data2;
|
||||||
|
@ -225,6 +247,17 @@ namespace Ryujinx.Headless.SDL2
|
||||||
return Renderer.GetHardwareInfo().GpuVendor;
|
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()
|
public void Render()
|
||||||
{
|
{
|
||||||
InitializeWindowRenderer();
|
InitializeWindowRenderer();
|
||||||
|
@ -233,6 +266,10 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
InitializeRenderer();
|
InitializeRenderer();
|
||||||
|
|
||||||
|
SetAntiAliasing();
|
||||||
|
|
||||||
|
SetScalingFilter();
|
||||||
|
|
||||||
_gpuVendorName = GetGpuVendorName();
|
_gpuVendorName = GetGpuVendorName();
|
||||||
|
|
||||||
Device.Gpu.Renderer.RunLoop(() =>
|
Device.Gpu.Renderer.RunLoop(() =>
|
||||||
|
|
Reference in a new issue