/ src / Ryujinx.Common / SystemInterop / WindowsMultimediaTimerResolution.cs
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  }