WindowsMultimediaTimerResolution.cs
1 using Ryujinx.Common.Logging; 2 using System; 3 using System.Runtime.CompilerServices; 4 using System.Runtime.InteropServices; 5 using System.Runtime.Versioning; 6 7 namespace Ryujinx.Common.SystemInterop 8 { 9 /// <summary> 10 /// Handle Windows Multimedia timer resolution. 11 /// </summary> 12 [SupportedOSPlatform("windows")] 13 public partial class WindowsMultimediaTimerResolution : IDisposable 14 { 15 [StructLayout(LayoutKind.Sequential)] 16 public struct TimeCaps 17 { 18 public uint wPeriodMin; 19 public uint wPeriodMax; 20 } 21 22 [LibraryImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)] 23 private static partial uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); 24 25 [LibraryImport("winmm.dll", EntryPoint = "timeBeginPeriod")] 26 private static partial uint TimeBeginPeriod(uint uMilliseconds); 27 28 [LibraryImport("winmm.dll", EntryPoint = "timeEndPeriod")] 29 private static partial uint TimeEndPeriod(uint uMilliseconds); 30 31 private uint _targetResolutionInMilliseconds; 32 private bool _isActive; 33 34 /// <summary> 35 /// Create a new <see cref="WindowsMultimediaTimerResolution"/> and activate the given resolution. 36 /// </summary> 37 /// <param name="targetResolutionInMilliseconds"></param> 38 public WindowsMultimediaTimerResolution(uint targetResolutionInMilliseconds) 39 { 40 _targetResolutionInMilliseconds = targetResolutionInMilliseconds; 41 42 EnsureResolutionSupport(); 43 Activate(); 44 } 45 46 private void EnsureResolutionSupport() 47 { 48 TimeCaps timeCaps = default; 49 50 uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf<TimeCaps>()); 51 52 if (result != 0) 53 { 54 Logger.Notice.Print(LogClass.Application, $"timeGetDevCaps failed with result: {result}"); 55 } 56 else 57 { 58 uint supportedTargetResolutionInMilliseconds = Math.Min(Math.Max(timeCaps.wPeriodMin, _targetResolutionInMilliseconds), timeCaps.wPeriodMax); 59 60 if (supportedTargetResolutionInMilliseconds != _targetResolutionInMilliseconds) 61 { 62 Logger.Notice.Print(LogClass.Application, $"Target resolution isn't supported by OS, using closest resolution: {supportedTargetResolutionInMilliseconds}ms"); 63 64 _targetResolutionInMilliseconds = supportedTargetResolutionInMilliseconds; 65 } 66 } 67 } 68 69 private void Activate() 70 { 71 uint result = TimeBeginPeriod(_targetResolutionInMilliseconds); 72 73 if (result != 0) 74 { 75 Logger.Notice.Print(LogClass.Application, $"timeBeginPeriod failed with result: {result}"); 76 } 77 else 78 { 79 _isActive = true; 80 } 81 } 82 83 private void Disable() 84 { 85 if (_isActive) 86 { 87 uint result = TimeEndPeriod(_targetResolutionInMilliseconds); 88 89 if (result != 0) 90 { 91 Logger.Notice.Print(LogClass.Application, $"timeEndPeriod failed with result: {result}"); 92 } 93 else 94 { 95 _isActive = false; 96 } 97 } 98 } 99 100 public void Dispose() 101 { 102 Dispose(true); 103 GC.SuppressFinalize(this); 104 } 105 106 protected virtual void Dispose(bool disposing) 107 { 108 if (disposing) 109 { 110 Disable(); 111 } 112 } 113 } 114 }