From c0579e41816bffaf80b78b578c8b2df3ef44a610 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 11 Sep 2024 01:35:17 +0800 Subject: [PATCH] Add integer scaling Upgrade XNAUI to 2.3.22 --- ClientCore/ClientConfiguration.cs | 16 +- ClientCore/Settings/UserINISettings.cs | 6 +- DTAConfig/OptionPanels/DisplayOptionsPanel.cs | 4 +- DTAConfig/ScreenResolution.cs | 28 +++- DXMainClient/DXGUI/GameClass.cs | 153 +++++++++++++----- Directory.Packages.props | 2 +- 6 files changed, 161 insertions(+), 48 deletions(-) diff --git a/ClientCore/ClientConfiguration.cs b/ClientCore/ClientConfiguration.cs index d80fb6a05..1bbc27258 100644 --- a/ClientCore/ClientConfiguration.cs +++ b/ClientCore/ClientConfiguration.cs @@ -16,6 +16,7 @@ public class ClientConfiguration private const string SETTINGS = "Settings"; private const string LINKS = "Links"; private const string TRANSLATIONS = "Translations"; + private const string USER_DEFAULTS = "UserDefaults"; private const string CLIENT_SETTINGS = "DTACnCNetClient.ini"; private const string GAME_OPTIONS = "GameOptions.ini"; @@ -193,7 +194,8 @@ public void RefreshSettings() public int MaximumRenderHeight => clientDefinitionsIni.GetIntValue(SETTINGS, "MaximumRenderHeight", 800); - public string[] RecommendedResolutions => clientDefinitionsIni.GetStringValue(SETTINGS, "RecommendedResolutions", "1280x720,2560x1440,3840x2160").Split(','); + public string[] RecommendedResolutions => clientDefinitionsIni.GetStringValue(SETTINGS, "RecommendedResolutions", + $"{MinimumRenderWidth}x{MinimumRenderHeight},{MaximumRenderWidth}x{MaximumRenderHeight}").Split(','); public string WindowTitle => clientDefinitionsIni.GetStringValue(SETTINGS, "WindowTitle", string.Empty) .L10N("INI:ClientDefinitions:WindowTitle"); @@ -405,6 +407,16 @@ public IEnumerable SupplementalMapFileExtensions #endregion + #region User default settings + + public bool UserDefault_BorderlessWindowedClient => clientDefinitionsIni.GetBooleanValue(USER_DEFAULTS, "BorderlessWindowedClient", true); + + public bool UserDefault_IntegerScaledClient => clientDefinitionsIni.GetBooleanValue(USER_DEFAULTS, "IntegerScaledClient", false); + + public bool UserDefault_WriteInstallationPathToRegistry => clientDefinitionsIni.GetBooleanValue(USER_DEFAULTS, "WriteInstallationPathToRegistry", true); + + #endregion + public List GetIRCServers() { List servers = []; @@ -419,7 +431,7 @@ public List GetIRCServers() } public bool DiscordIntegrationGloballyDisabled => string.IsNullOrWhiteSpace(DiscordAppId) || DisableDiscordIntegration; - + public OSVersion GetOperatingSystemVersion() { #if NETFRAMEWORK diff --git a/ClientCore/Settings/UserINISettings.cs b/ClientCore/Settings/UserINISettings.cs index 671c823fe..9a0829d6f 100644 --- a/ClientCore/Settings/UserINISettings.cs +++ b/ClientCore/Settings/UserINISettings.cs @@ -63,7 +63,8 @@ protected UserINISettings(IniFile iniFile) Renderer = new StringSetting(iniFile, COMPATIBILITY, "Renderer", string.Empty); WindowedMode = new BoolSetting(iniFile, VIDEO, WINDOWED_MODE_KEY, false); BorderlessWindowedMode = new BoolSetting(iniFile, VIDEO, "NoWindowFrame", false); - BorderlessWindowedClient = new BoolSetting(iniFile, VIDEO, "BorderlessWindowedClient", true); + BorderlessWindowedClient = new BoolSetting(iniFile, VIDEO, "BorderlessWindowedClient", ClientConfiguration.Instance.UserDefault_BorderlessWindowedClient); + IntegerScaledClient = new BoolSetting(iniFile, VIDEO, "IntegerScaledClient", ClientConfiguration.Instance.UserDefault_IntegerScaledClient); ClientFPS = new IntSetting(iniFile, VIDEO, "ClientFPS", 60); DisplayToggleableExtraTextures = new BoolSetting(iniFile, VIDEO, "DisplayToggleableExtraTextures", true); @@ -86,7 +87,7 @@ protected UserINISettings(IniFile iniFile) ChatColor = new IntSetting(iniFile, MULTIPLAYER, "ChatColor", -1); LANChatColor = new IntSetting(iniFile, MULTIPLAYER, "LANChatColor", -1); PingUnofficialCnCNetTunnels = new BoolSetting(iniFile, MULTIPLAYER, "PingCustomTunnels", true); - WritePathToRegistry = new BoolSetting(iniFile, OPTIONS, "WriteInstallationPathToRegistry", true); + WritePathToRegistry = new BoolSetting(iniFile, OPTIONS, "WriteInstallationPathToRegistry", ClientConfiguration.Instance.UserDefault_WriteInstallationPathToRegistry); PlaySoundOnGameHosted = new BoolSetting(iniFile, MULTIPLAYER, "PlaySoundOnGameHosted", true); SkipConnectDialog = new BoolSetting(iniFile, MULTIPLAYER, "SkipConnectDialog", false); PersistentMode = new BoolSetting(iniFile, MULTIPLAYER, "PersistentMode", false); @@ -151,6 +152,7 @@ protected UserINISettings(IniFile iniFile) public IntSetting ClientResolutionX { get; set; } public IntSetting ClientResolutionY { get; set; } public BoolSetting BorderlessWindowedClient { get; private set; } + public BoolSetting IntegerScaledClient { get; private set; } public IntSetting ClientFPS { get; private set; } public BoolSetting DisplayToggleableExtraTextures { get; private set; } diff --git a/DTAConfig/OptionPanels/DisplayOptionsPanel.cs b/DTAConfig/OptionPanels/DisplayOptionsPanel.cs index 252da14d2..d2adf72bb 100644 --- a/DTAConfig/OptionPanels/DisplayOptionsPanel.cs +++ b/DTAConfig/OptionPanels/DisplayOptionsPanel.cs @@ -86,7 +86,7 @@ public override void Initialize() var maximumIngameResolution = new ScreenResolution(ClientConfiguration.Instance.MaximumIngameWidth, ClientConfiguration.Instance.MaximumIngameHeight); #if XNA - if (!ScreenResolution.HiDefLimitResolution.Fit(maximumIngameResolution)) + if (!ScreenResolution.HiDefLimitResolution.Fits(maximumIngameResolution)) maximumIngameResolution = ScreenResolution.HiDefLimitResolution; #endif @@ -769,6 +769,8 @@ public override bool Save() clientRes.Height != IniSettings.ClientResolutionY.Value) restartRequired = true; + // TODO: since DTAConfig must not rely on DXMainClient, we can't notify the client to dynamically change the resolution or togging borderless windowed mode. Thus, we need to restart the client as a workaround. + (IniSettings.ClientResolutionX.Value, IniSettings.ClientResolutionY.Value) = clientRes; if (IniSettings.BorderlessWindowedClient.Value != chkBorderlessClient.Checked) diff --git a/DTAConfig/ScreenResolution.cs b/DTAConfig/ScreenResolution.cs index 97c01c559..1b13ef38c 100644 --- a/DTAConfig/ScreenResolution.cs +++ b/DTAConfig/ScreenResolution.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace DTAConfig @@ -28,6 +29,12 @@ public ScreenResolution(int width, int height) Height = height; } + public ScreenResolution(Rectangle rectangle) + { + Width = rectangle.Width; + Height = rectangle.Height; + } + public ScreenResolution(string resolution) { List resolutionList = resolution.Trim().Split('x').Take(2).Select(int.Parse).ToList(); @@ -51,12 +58,16 @@ public void Deconstruct(out int width, out int height) public static implicit operator (int Width, int Height)(ScreenResolution resolution) => new(resolution.Width, resolution.Height); - public bool Fit(ScreenResolution child) => this.Width >= child.Width && this.Height >= child.Height; + public bool Fits(ScreenResolution child) => this.Width >= child.Width && this.Height >= child.Height; public int CompareTo(ScreenResolution other) => (this.Width, this.Height).CompareTo(other); // Accessing GraphicsAdapter.DefaultAdapter requiring DXMainClient.GameClass has been constructed. Lazy loading prevents possible null reference issues for now. private static ScreenResolution _desktopResolution = null; + + /// + /// The resolution of primary monitor. + /// public static ScreenResolution DesktopResolution => _desktopResolution ??= new(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height); @@ -64,12 +75,16 @@ public void Deconstruct(out int width, out int height) public static ScreenResolution HiDefLimitResolution { get; } = "3840x3840"; private static ScreenResolution _safeMaximumResolution = null; + + /// + /// The resolution of primary monitor, or the maximum resolution supported by the graphic profile, whichever is smaller. + /// public static ScreenResolution SafeMaximumResolution { get { #if XNA - return _safeMaximumResolution ??= HiDefLimitResolution.Fit(DesktopResolution) ? DesktopResolution : HiDefLimitResolution; + return _safeMaximumResolution ??= HiDefLimitResolution.Fits(DesktopResolution) ? DesktopResolution : HiDefLimitResolution; #else return _safeMaximumResolution ??= DesktopResolution; #endif @@ -77,6 +92,10 @@ public static ScreenResolution SafeMaximumResolution } private static ScreenResolution _safeFullScreenResolution = null; + + /// + /// The maximum resolution supported by the graphic profile, or the largest full screen resolution supported by the primary monitor, whichever is smaller. + /// public static ScreenResolution SafeFullScreenResolution => _safeFullScreenResolution ??= GetFullScreenResolutions(minWidth: 800, minHeight: 600).Max(); public static SortedSet GetFullScreenResolutions(int minWidth, int minHeight) => @@ -123,7 +142,7 @@ public SortedSet GetIntegerScaledResolutions(ScreenResolution { ScreenResolution scaledResolution = (this.Width * i, this.Height * i); - if (maxResolution.Fit(scaledResolution)) + if (maxResolution.Fits(scaledResolution)) resolutions.Add(scaledResolution); else break; @@ -149,6 +168,9 @@ public static SortedSet GetWindowedResolutions(IEnumerable + //{ + // ScreenResolution currentWindowSize = new(wm.Game.Window.ClientBounds); + + // if (currentWindowSize != lastWindowSizeCaptured) + // { + // Logger.Log($"Window size changed from {lastWindowSizeCaptured} to {currentWindowSize}."); + // lastWindowSizeCaptured = currentWindowSize; + // SetGraphicsMode(wm, currentWindowSize.Width, currentWindowSize.Height, centerOnScreen: false); + // } + //}; + } #endif wm.Cursor.Textures = new Texture2D[] @@ -311,14 +338,34 @@ private void InitializeUISettings() /// TODO move to some helper class? /// /// The window manager - public static void SetGraphicsMode(WindowManager wm) + /// Whether to center the client window on the screen + public static void SetGraphicsMode(WindowManager wm, bool centerOnScreen = true) { - var clientConfiguration = ClientConfiguration.Instance; - int windowWidth = UserINISettings.Instance.ClientResolutionX; int windowHeight = UserINISettings.Instance.ClientResolutionY; + SetGraphicsMode(wm, windowWidth, windowHeight, centerOnScreen); + } + + /// + /// The viewport width + /// The viewport height + public static void SetGraphicsMode(WindowManager wm, int windowWidth, int windowHeight, bool centerOnScreen = true) + { bool borderlessWindowedClient = UserINISettings.Instance.BorderlessWindowedClient; + bool integerScale = UserINISettings.Instance.IntegerScaledClient; + + SetGraphicsMode(wm, windowWidth, windowHeight, borderlessWindowedClient, integerScale, centerOnScreen); + } + + /// + /// Whether to use borderless windowed mode + /// Whether to use integer scaling + public static void SetGraphicsMode(WindowManager wm, int windowWidth, int windowHeight, bool borderlessWindowedClient, bool integerScale, bool centerOnScreen = true) + { + var clientConfiguration = ClientConfiguration.Instance; + + wm.IntegerScalingOnly = integerScale; (int desktopWidth, int desktopHeight) = ScreenResolution.SafeMaximumResolution; @@ -338,53 +385,78 @@ public static void SetGraphicsMode(WindowManager wm) int renderResolutionX = 0; int renderResolutionY = 0; - int initialXRes = Math.Max(windowWidth, clientConfiguration.MinimumRenderWidth); - initialXRes = Math.Min(initialXRes, clientConfiguration.MaximumRenderWidth); - - int initialYRes = Math.Max(windowHeight, clientConfiguration.MinimumRenderHeight); - initialYRes = Math.Min(initialYRes, clientConfiguration.MaximumRenderHeight); + if (!integerScale || windowWidth < clientConfiguration.MinimumRenderWidth || windowHeight < clientConfiguration.MinimumRenderHeight) + { + int initialXRes = Math.Max(windowWidth, clientConfiguration.MinimumRenderWidth); + initialXRes = Math.Min(initialXRes, clientConfiguration.MaximumRenderWidth); - double xRatio = (windowWidth) / (double)initialXRes; - double yRatio = (windowHeight) / (double)initialYRes; + int initialYRes = Math.Max(windowHeight, clientConfiguration.MinimumRenderHeight); + initialYRes = Math.Min(initialYRes, clientConfiguration.MaximumRenderHeight); - double ratio = xRatio > yRatio ? yRatio : xRatio; + double xRatio = (windowWidth) / (double)initialXRes; + double yRatio = (windowHeight) / (double)initialYRes; - if ((windowWidth == 1366 || windowWidth == 1360) && windowHeight == 768) - { - renderResolutionX = windowWidth; - renderResolutionY = windowHeight; - } + double ratio = xRatio > yRatio ? yRatio : xRatio; - if (ratio > 1.0) - { - // Check whether we could sharp-scale our client window - for (int i = 2; i <= ScreenResolution.MAX_INT_SCALE; i++) + if (720 <= clientConfiguration.MaximumRenderHeight && clientConfiguration.MaximumRenderHeight < 768) { - int sharpScaleRenderResX = windowWidth / i; - int sharpScaleRenderResY = windowHeight / i; + // Most client interface has been designed for 1280x720 or 1280x800. + // 1280x720 upscaled to 1366x768 doesn't look great, so we allow players with 1366x768 to use their native resolution with small black bars on the sides + // This behavior is enforced even if IntegerScaledClient is turned off. + if ((windowWidth == 1366 || windowWidth == 1360) && windowHeight == 768) + { + renderResolutionX = windowWidth; + renderResolutionY = windowHeight; + } + } - if (sharpScaleRenderResX >= clientConfiguration.MinimumRenderWidth && - sharpScaleRenderResX <= clientConfiguration.MaximumRenderWidth && - sharpScaleRenderResY >= clientConfiguration.MinimumRenderHeight && - sharpScaleRenderResY <= clientConfiguration.MaximumRenderHeight) + if (ratio > 1.0) + { + // Check whether we could sharp-scale our client window + for (int i = 2; i <= ScreenResolution.MAX_INT_SCALE; i++) { - renderResolutionX = sharpScaleRenderResX; - renderResolutionY = sharpScaleRenderResY; - break; + int sharpScaleRenderResX = windowWidth / i; + int sharpScaleRenderResY = windowHeight / i; + + if (sharpScaleRenderResX >= clientConfiguration.MinimumRenderWidth && + sharpScaleRenderResX <= clientConfiguration.MaximumRenderWidth && + sharpScaleRenderResY >= clientConfiguration.MinimumRenderHeight && + sharpScaleRenderResY <= clientConfiguration.MaximumRenderHeight) + { + renderResolutionX = sharpScaleRenderResX; + renderResolutionY = sharpScaleRenderResY; + break; + } } } - } - if (renderResolutionX == 0 || renderResolutionY == 0) - { - renderResolutionX = initialXRes; - renderResolutionY = initialYRes; + if (renderResolutionX == 0 || renderResolutionY == 0) + { + renderResolutionX = initialXRes; + renderResolutionY = initialYRes; - if (ratio == xRatio) - renderResolutionY = (int)(windowHeight / ratio); + if (ratio == xRatio) + renderResolutionY = (int)(windowHeight / ratio); + } + } + else + { + // Compute integer scale ratio using minimum render resolution + // Note: this means we prefer larger scale ratio than render resolution. + // This policy works best when maximum and minimum render resolution are close. + int xScale = windowWidth / clientConfiguration.MinimumRenderWidth; + int yScale = windowHeight / clientConfiguration.MinimumRenderHeight; + int scale = Math.Min(xScale, yScale); + + // Compute render resolution + renderResolutionX = Math.Min(clientConfiguration.MaximumRenderWidth, + clientConfiguration.MinimumRenderWidth + (windowWidth - clientConfiguration.MinimumRenderWidth * scale) / scale); + renderResolutionY = Math.Min(clientConfiguration.MaximumRenderHeight, + clientConfiguration.MinimumRenderHeight + (windowHeight - clientConfiguration.MinimumRenderHeight * scale) / scale); } wm.SetBorderlessMode(borderlessWindowedClient); + #if !XNA if (borderlessWindowedClient) @@ -404,7 +476,10 @@ public static void SetGraphicsMode(WindowManager wm) } #endif - wm.CenterOnScreen(); + if (centerOnScreen) + wm.CenterOnScreen(); + + Logger.Log("Setting render resolution to " + renderResolutionX + "x" + renderResolutionY + ". Integer scaling: " + integerScale); wm.SetRenderResolution(renderResolutionX, renderResolutionY); } } diff --git a/Directory.Packages.props b/Directory.Packages.props index 182e8f440..522e63ded 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ true - 2.3.20 + 2.3.22 8.0.0