From 0fbcd630bc57885d6b94fd3c4b3546493e09059e Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 15 Dec 2022 12:07:31 -0500 Subject: Replace `DllImport` usage with `LibraryImport` (#4084) * Replace usage of `DllImport` with `LibraryImport` * Mark methods as `partial` * Marshalling * More `partial` & marshalling * More `partial` and marshalling * More partial and marshalling * Update GdiPlusHelper to LibraryImport * Unicorn * More Partial * Marshal * Specify EntryPoint * Specify EntryPoint * Change GlobalMemoryStatusEx to LibraryImport * Change RegisterClassEx to LibraryImport * Define EntryPoints * Update Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Move return mashal * Remove calling convention specification * Remove calling conventions * Update Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx/Modules/Updater/Updater.cs Co-authored-by: Mary-nyan * Update Ryujinx.Ava/Modules/Updater/Updater.cs Co-authored-by: Mary-nyan Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Co-authored-by: Mary-nyan --- .../GraphicsDriver/NVThreadedOptimization.cs | 6 +- .../Memory/PartialUnmaps/PartialUnmapState.cs | 21 ++-- Ryujinx.Common/System/DisplaySleep.cs | 35 ------- Ryujinx.Common/System/ForceDpiAware.cs | 95 ----------------- Ryujinx.Common/System/GdiPlusHelper.cs | 76 -------------- .../System/WindowsMultimediaTimerResolution.cs | 114 --------------------- Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs | 18 ++-- Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs | 12 +-- Ryujinx.Common/SystemInterop/DisplaySleep.cs | 35 +++++++ Ryujinx.Common/SystemInterop/ForceDpiAware.cs | 96 +++++++++++++++++ Ryujinx.Common/SystemInterop/GdiPlusHelper.cs | 76 ++++++++++++++ .../WindowsMultimediaTimerResolution.cs | 114 +++++++++++++++++++++ 12 files changed, 351 insertions(+), 347 deletions(-) delete mode 100644 Ryujinx.Common/System/DisplaySleep.cs delete mode 100644 Ryujinx.Common/System/ForceDpiAware.cs delete mode 100644 Ryujinx.Common/System/GdiPlusHelper.cs delete mode 100644 Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs create mode 100644 Ryujinx.Common/SystemInterop/DisplaySleep.cs create mode 100644 Ryujinx.Common/SystemInterop/ForceDpiAware.cs create mode 100644 Ryujinx.Common/SystemInterop/GdiPlusHelper.cs create mode 100644 Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs (limited to 'Ryujinx.Common') diff --git a/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs b/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs index d21d3555..c5be6e37 100644 --- a/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs +++ b/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Common.GraphicsDriver { - static class NVThreadedOptimization + static partial class NVThreadedOptimization { private const string ProfileName = "Ryujinx Nvidia Profile"; @@ -19,8 +19,8 @@ namespace Ryujinx.Common.GraphicsDriver private const uint NvAPI_DRS_SaveSettings_ID = 0xFCBC7E14; private const uint NvAPI_DRS_DestroySession_ID = 0x0DAD9CFF8; - [DllImport("nvapi64")] - private static extern IntPtr nvapi_QueryInterface(uint id); + [LibraryImport("nvapi64")] + private static partial IntPtr nvapi_QueryInterface(uint id); private delegate int NvAPI_InitializeDelegate(); private static NvAPI_InitializeDelegate NvAPI_Initialize; diff --git a/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs b/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs index 7a14e616..3463d06c 100644 --- a/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs +++ b/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using System.Runtime.Versioning; using System.Threading; @@ -12,7 +13,7 @@ namespace Ryujinx.Common.Memory.PartialUnmaps /// State for partial unmaps. Intended to be used on Windows. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PartialUnmapState + public partial struct PartialUnmapState { public NativeReaderWriterLock PartialUnmapLock; public int PartialUnmapsCount; @@ -25,20 +26,22 @@ namespace Ryujinx.Common.Memory.PartialUnmaps public readonly static IntPtr GlobalState; [SupportedOSPlatform("windows")] - [DllImport("kernel32.dll")] - public static extern int GetCurrentThreadId(); + [LibraryImport("kernel32.dll")] + public static partial int GetCurrentThreadId(); [SupportedOSPlatform("windows")] - [DllImport("kernel32.dll", SetLastError = true)] - static extern IntPtr OpenThread(int dwDesiredAccess, bool bInheritHandle, uint dwThreadId); + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial IntPtr OpenThread(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId); [SupportedOSPlatform("windows")] - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool CloseHandle(IntPtr hObject); + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + public static partial bool CloseHandle(IntPtr hObject); [SupportedOSPlatform("windows")] - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode); + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode); /// /// Creates a global static PartialUnmapState and populates the field offsets. diff --git a/Ryujinx.Common/System/DisplaySleep.cs b/Ryujinx.Common/System/DisplaySleep.cs deleted file mode 100644 index bad964b9..00000000 --- a/Ryujinx.Common/System/DisplaySleep.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.Common.System -{ - public class DisplaySleep - { - [Flags] - enum EXECUTION_STATE : uint - { - ES_CONTINUOUS = 0x80000000, - ES_DISPLAY_REQUIRED = 0x00000002, - ES_SYSTEM_REQUIRED = 0x00000001 - } - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); - - static public void Prevent() - { - if (OperatingSystem.IsWindows()) - { - SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED); - } - } - - static public void Restore() - { - if (OperatingSystem.IsWindows()) - { - SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS); - } - } - } -} diff --git a/Ryujinx.Common/System/ForceDpiAware.cs b/Ryujinx.Common/System/ForceDpiAware.cs deleted file mode 100644 index 8d19876e..00000000 --- a/Ryujinx.Common/System/ForceDpiAware.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Ryujinx.Common.Logging; -using System; -using System.Globalization; -using System.Runtime.InteropServices; - -namespace Ryujinx.Common.System -{ - public static class ForceDpiAware - { - [DllImport("user32.dll")] - private static extern bool SetProcessDPIAware(); - - private const string X11LibraryName = "libX11.so.6"; - - [DllImport(X11LibraryName)] - private static extern IntPtr XOpenDisplay(string display); - - [DllImport(X11LibraryName)] - private static extern IntPtr XGetDefault(IntPtr display, string program, string option); - - [DllImport(X11LibraryName)] - private static extern int XDisplayWidth(IntPtr display, int screenNumber); - - [DllImport(X11LibraryName)] - private static extern int XDisplayWidthMM(IntPtr display, int screenNumber); - - [DllImport(X11LibraryName)] - private static extern int XCloseDisplay(IntPtr display); - - private static readonly double _standardDpiScale = 96.0; - private static readonly double _maxScaleFactor = 1.25; - - /// - /// Marks the application as DPI-Aware when running on the Windows operating system. - /// - public static void Windows() - { - // Make process DPI aware for proper window sizing on high-res screens. - if (OperatingSystem.IsWindowsVersionAtLeast(6)) - { - SetProcessDPIAware(); - } - } - - public static double GetActualScaleFactor() - { - double userDpiScale = 96.0; - - try - { - if (OperatingSystem.IsWindows()) - { - userDpiScale = GdiPlusHelper.GetDpiX(IntPtr.Zero); - } - else if (OperatingSystem.IsLinux()) - { - string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower(); - - if (xdgSessionType == null || xdgSessionType == "x11") - { - IntPtr display = XOpenDisplay(null); - string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi")); - if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale)) - { - userDpiScale = (double)XDisplayWidth(display, 0) * 25.4 / (double)XDisplayWidthMM(display, 0); - } - XCloseDisplay(display); - } - else if (xdgSessionType == "wayland") - { - // TODO - Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Wayland not yet supported"); - } - else - { - Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}"); - } - } - } - catch (Exception e) - { - Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}"); - } - - return userDpiScale; - } - - public static double GetWindowScaleFactor() - { - double userDpiScale = GetActualScaleFactor(); - - return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor); - } - } -} diff --git a/Ryujinx.Common/System/GdiPlusHelper.cs b/Ryujinx.Common/System/GdiPlusHelper.cs deleted file mode 100644 index c084c651..00000000 --- a/Ryujinx.Common/System/GdiPlusHelper.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace Ryujinx.Common.System -{ - [SupportedOSPlatform("windows")] - public static class GdiPlusHelper - { - private const string LibraryName = "gdiplus.dll"; - - private static readonly IntPtr _initToken; - - static GdiPlusHelper() - { - CheckStatus(GdiplusStartup(out _initToken, StartupInputEx.Default, out _)); - } - - private static void CheckStatus(int gdiStatus) - { - if (gdiStatus != 0) - { - throw new Exception($"GDI Status Error: {gdiStatus}"); - } - } - - private struct StartupInputEx - { - public int GdiplusVersion; - -#pragma warning disable CS0649 - public IntPtr DebugEventCallback; - public int SuppressBackgroundThread; - public int SuppressExternalCodecs; - public int StartupParameters; -#pragma warning restore CS0649 - - public static StartupInputEx Default => new StartupInputEx - { - // We assume Windows 8 and upper - GdiplusVersion = 2, - DebugEventCallback = IntPtr.Zero, - SuppressBackgroundThread = 0, - SuppressExternalCodecs = 0, - StartupParameters = 0, - }; - } - - private struct StartupOutput - { - public IntPtr NotificationHook; - public IntPtr NotificationUnhook; - } - - [DllImport(LibraryName)] - private static extern int GdiplusStartup(out IntPtr token, in StartupInputEx input, out StartupOutput output); - - [DllImport(LibraryName)] - private static extern int GdipCreateFromHWND(IntPtr hwnd, out IntPtr graphics); - - [DllImport(LibraryName)] - private static extern int GdipDeleteGraphics(IntPtr graphics); - - [DllImport(LibraryName)] - private static extern int GdipGetDpiX(IntPtr graphics, out float dpi); - - public static float GetDpiX(IntPtr hwnd) - { - CheckStatus(GdipCreateFromHWND(hwnd, out IntPtr graphicsHandle)); - CheckStatus(GdipGetDpiX(graphicsHandle, out float result)); - CheckStatus(GdipDeleteGraphics(graphicsHandle)); - - return result; - } - } -} diff --git a/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs b/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs deleted file mode 100644 index d19fbe73..00000000 --- a/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Ryujinx.Common.Logging; -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace Ryujinx.Common.System -{ - /// - /// Handle Windows Multimedia timer resolution. - /// - [SupportedOSPlatform("windows")] - public class WindowsMultimediaTimerResolution : IDisposable - { - [StructLayout(LayoutKind.Sequential)] - public struct TimeCaps - { - public uint wPeriodMin; - public uint wPeriodMax; - }; - - [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)] - private static extern uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); - - [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] - private static extern uint TimeBeginPeriod(uint uMilliseconds); - - [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] - private static extern uint TimeEndPeriod(uint uMilliseconds); - - private uint _targetResolutionInMilliseconds; - private bool _isActive; - - /// - /// Create a new and activate the given resolution. - /// - /// - public WindowsMultimediaTimerResolution(uint targetResolutionInMilliseconds) - { - _targetResolutionInMilliseconds = targetResolutionInMilliseconds; - - EnsureResolutionSupport(); - Activate(); - } - - private void EnsureResolutionSupport() - { - TimeCaps timeCaps = default; - - uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf()); - - if (result != 0) - { - Logger.Notice.Print(LogClass.Application, $"timeGetDevCaps failed with result: {result}"); - } - else - { - uint supportedTargetResolutionInMilliseconds = Math.Min(Math.Max(timeCaps.wPeriodMin, _targetResolutionInMilliseconds), timeCaps.wPeriodMax); - - if (supportedTargetResolutionInMilliseconds != _targetResolutionInMilliseconds) - { - Logger.Notice.Print(LogClass.Application, $"Target resolution isn't supported by OS, using closest resolution: {supportedTargetResolutionInMilliseconds}ms"); - - _targetResolutionInMilliseconds = supportedTargetResolutionInMilliseconds; - } - } - } - - private void Activate() - { - uint result = TimeBeginPeriod(_targetResolutionInMilliseconds); - - if (result != 0) - { - Logger.Notice.Print(LogClass.Application, $"timeBeginPeriod failed with result: {result}"); - } - else - { - _isActive = true; - } - } - - private void Disable() - { - if (_isActive) - { - uint result = TimeEndPeriod(_targetResolutionInMilliseconds); - - if (result != 0) - { - Logger.Notice.Print(LogClass.Application, $"timeEndPeriod failed with result: {result}"); - } - else - { - _isActive = false; - } - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - Disable(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs b/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs index 3fcb1a25..ad022bdf 100644 --- a/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs +++ b/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs @@ -8,7 +8,7 @@ using Ryujinx.Common.Logging; namespace Ryujinx.Common.SystemInfo { [SupportedOSPlatform("macos")] - class MacOSSystemInfo : SystemInfo + partial class MacOSSystemInfo : SystemInfo { internal MacOSSystemInfo() { @@ -60,8 +60,8 @@ namespace Ryujinx.Common.SystemInfo private const string SystemLibraryName = "libSystem.dylib"; - [DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)] - private static extern int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); private static int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize) { @@ -116,11 +116,11 @@ namespace Ryujinx.Common.SystemInfo return res; } - [DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)] - private static extern uint mach_host_self(); + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial uint mach_host_self(); - [DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)] - private static extern int host_page_size(uint host, ref uint out_page_size); + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int host_page_size(uint host, ref uint out_page_size); [StructLayout(LayoutKind.Sequential, Pack = 8)] struct VMStatistics64 @@ -151,7 +151,7 @@ namespace Ryujinx.Common.SystemInfo public ulong TotalUncompressedPagesInCompressor; } - [DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)] - private static extern int host_statistics64(uint host_priv, int host_flavor, ref VMStatistics64 host_info64_out, ref uint host_info64_outCnt); + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int host_statistics64(uint host_priv, int host_flavor, ref VMStatistics64 host_info64_out, ref uint host_info64_outCnt); } } \ No newline at end of file diff --git a/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs b/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs index ffce665e..11f0785e 100644 --- a/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs +++ b/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs @@ -7,7 +7,7 @@ using Ryujinx.Common.Logging; namespace Ryujinx.Common.SystemInfo { [SupportedOSPlatform("windows")] - class WindowsSystemInfo : SystemInfo + partial class WindowsSystemInfo : SystemInfo { internal WindowsSystemInfo() { @@ -18,7 +18,7 @@ namespace Ryujinx.Common.SystemInfo private static (ulong Total, ulong Available) GetMemoryStats() { MemoryStatusEx memStatus = new MemoryStatusEx(); - if (GlobalMemoryStatusEx(memStatus)) + if (GlobalMemoryStatusEx(ref memStatus)) { return (memStatus.TotalPhys, memStatus.AvailPhys); // Bytes } @@ -45,8 +45,8 @@ namespace Ryujinx.Common.SystemInfo return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER").Trim(); } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - private class MemoryStatusEx + [StructLayout(LayoutKind.Sequential)] + private struct MemoryStatusEx { public uint Length; public uint MemoryLoad; @@ -64,9 +64,9 @@ namespace Ryujinx.Common.SystemInfo } } + [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern bool GlobalMemoryStatusEx([In, Out] MemoryStatusEx lpBuffer); + private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer); private static ManagementObjectCollection GetWMIObjects(string scope, string query) { diff --git a/Ryujinx.Common/SystemInterop/DisplaySleep.cs b/Ryujinx.Common/SystemInterop/DisplaySleep.cs new file mode 100644 index 00000000..5a1f66f5 --- /dev/null +++ b/Ryujinx.Common/SystemInterop/DisplaySleep.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.SystemInterop +{ + public partial class DisplaySleep + { + [Flags] + enum EXECUTION_STATE : uint + { + ES_CONTINUOUS = 0x80000000, + ES_DISPLAY_REQUIRED = 0x00000002, + ES_SYSTEM_REQUIRED = 0x00000001 + } + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); + + static public void Prevent() + { + if (OperatingSystem.IsWindows()) + { + SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED); + } + } + + static public void Restore() + { + if (OperatingSystem.IsWindows()) + { + SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS); + } + } + } +} diff --git a/Ryujinx.Common/SystemInterop/ForceDpiAware.cs b/Ryujinx.Common/SystemInterop/ForceDpiAware.cs new file mode 100644 index 00000000..f17612a6 --- /dev/null +++ b/Ryujinx.Common/SystemInterop/ForceDpiAware.cs @@ -0,0 +1,96 @@ +using Ryujinx.Common.Logging; +using System; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.SystemInterop +{ + public static partial class ForceDpiAware + { + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetProcessDPIAware(); + + private const string X11LibraryName = "libX11.so.6"; + + [LibraryImport(X11LibraryName)] + private static partial IntPtr XOpenDisplay([MarshalAs(UnmanagedType.LPStr)] string display); + + [LibraryImport(X11LibraryName)] + private static partial IntPtr XGetDefault(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string program, [MarshalAs(UnmanagedType.LPStr)] string option); + + [LibraryImport(X11LibraryName)] + private static partial int XDisplayWidth(IntPtr display, int screenNumber); + + [LibraryImport(X11LibraryName)] + private static partial int XDisplayWidthMM(IntPtr display, int screenNumber); + + [LibraryImport(X11LibraryName)] + private static partial int XCloseDisplay(IntPtr display); + + private static readonly double _standardDpiScale = 96.0; + private static readonly double _maxScaleFactor = 1.25; + + /// + /// Marks the application as DPI-Aware when running on the Windows operating system. + /// + public static void Windows() + { + // Make process DPI aware for proper window sizing on high-res screens. + if (OperatingSystem.IsWindowsVersionAtLeast(6)) + { + SetProcessDPIAware(); + } + } + + public static double GetActualScaleFactor() + { + double userDpiScale = 96.0; + + try + { + if (OperatingSystem.IsWindows()) + { + userDpiScale = GdiPlusHelper.GetDpiX(IntPtr.Zero); + } + else if (OperatingSystem.IsLinux()) + { + string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower(); + + if (xdgSessionType == null || xdgSessionType == "x11") + { + IntPtr display = XOpenDisplay(null); + string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi")); + if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale)) + { + userDpiScale = (double)XDisplayWidth(display, 0) * 25.4 / (double)XDisplayWidthMM(display, 0); + } + XCloseDisplay(display); + } + else if (xdgSessionType == "wayland") + { + // TODO + Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Wayland not yet supported"); + } + else + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}"); + } + } + } + catch (Exception e) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}"); + } + + return userDpiScale; + } + + public static double GetWindowScaleFactor() + { + double userDpiScale = GetActualScaleFactor(); + + return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor); + } + } +} diff --git a/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs b/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs new file mode 100644 index 00000000..1001424d --- /dev/null +++ b/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs @@ -0,0 +1,76 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Common.SystemInterop +{ + [SupportedOSPlatform("windows")] + public static partial class GdiPlusHelper + { + private const string LibraryName = "gdiplus.dll"; + + private static readonly IntPtr _initToken; + + static GdiPlusHelper() + { + CheckStatus(GdiplusStartup(out _initToken, StartupInputEx.Default, out _)); + } + + private static void CheckStatus(int gdiStatus) + { + if (gdiStatus != 0) + { + throw new Exception($"GDI Status Error: {gdiStatus}"); + } + } + + private struct StartupInputEx + { + public int GdiplusVersion; + +#pragma warning disable CS0649 + public IntPtr DebugEventCallback; + public int SuppressBackgroundThread; + public int SuppressExternalCodecs; + public int StartupParameters; +#pragma warning restore CS0649 + + public static StartupInputEx Default => new StartupInputEx + { + // We assume Windows 8 and upper + GdiplusVersion = 2, + DebugEventCallback = IntPtr.Zero, + SuppressBackgroundThread = 0, + SuppressExternalCodecs = 0, + StartupParameters = 0, + }; + } + + private struct StartupOutput + { + public IntPtr NotificationHook; + public IntPtr NotificationUnhook; + } + + [LibraryImport(LibraryName)] + private static partial int GdiplusStartup(out IntPtr token, in StartupInputEx input, out StartupOutput output); + + [LibraryImport(LibraryName)] + private static partial int GdipCreateFromHWND(IntPtr hwnd, out IntPtr graphics); + + [LibraryImport(LibraryName)] + private static partial int GdipDeleteGraphics(IntPtr graphics); + + [LibraryImport(LibraryName)] + private static partial int GdipGetDpiX(IntPtr graphics, out float dpi); + + public static float GetDpiX(IntPtr hwnd) + { + CheckStatus(GdipCreateFromHWND(hwnd, out IntPtr graphicsHandle)); + CheckStatus(GdipGetDpiX(graphicsHandle, out float result)); + CheckStatus(GdipDeleteGraphics(graphicsHandle)); + + return result; + } + } +} diff --git a/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs b/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs new file mode 100644 index 00000000..a4fbf0bd --- /dev/null +++ b/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs @@ -0,0 +1,114 @@ +using Ryujinx.Common.Logging; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Common.SystemInterop +{ + /// + /// Handle Windows Multimedia timer resolution. + /// + [SupportedOSPlatform("windows")] + public partial class WindowsMultimediaTimerResolution : IDisposable + { + [StructLayout(LayoutKind.Sequential)] + public struct TimeCaps + { + public uint wPeriodMin; + public uint wPeriodMax; + }; + + [LibraryImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)] + private static partial uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); + + [LibraryImport("winmm.dll", EntryPoint = "timeBeginPeriod")] + private static partial uint TimeBeginPeriod(uint uMilliseconds); + + [LibraryImport("winmm.dll", EntryPoint = "timeEndPeriod")] + private static partial uint TimeEndPeriod(uint uMilliseconds); + + private uint _targetResolutionInMilliseconds; + private bool _isActive; + + /// + /// Create a new and activate the given resolution. + /// + /// + public WindowsMultimediaTimerResolution(uint targetResolutionInMilliseconds) + { + _targetResolutionInMilliseconds = targetResolutionInMilliseconds; + + EnsureResolutionSupport(); + Activate(); + } + + private void EnsureResolutionSupport() + { + TimeCaps timeCaps = default; + + uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf()); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeGetDevCaps failed with result: {result}"); + } + else + { + uint supportedTargetResolutionInMilliseconds = Math.Min(Math.Max(timeCaps.wPeriodMin, _targetResolutionInMilliseconds), timeCaps.wPeriodMax); + + if (supportedTargetResolutionInMilliseconds != _targetResolutionInMilliseconds) + { + Logger.Notice.Print(LogClass.Application, $"Target resolution isn't supported by OS, using closest resolution: {supportedTargetResolutionInMilliseconds}ms"); + + _targetResolutionInMilliseconds = supportedTargetResolutionInMilliseconds; + } + } + } + + private void Activate() + { + uint result = TimeBeginPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeBeginPeriod failed with result: {result}"); + } + else + { + _isActive = true; + } + } + + private void Disable() + { + if (_isActive) + { + uint result = TimeEndPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeEndPeriod failed with result: {result}"); + } + else + { + _isActive = false; + } + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Disable(); + } + } + } +} \ No newline at end of file -- cgit v1.2.3