From c1b7340023b42161d55993de2e40baad68915b86 Mon Sep 17 00:00:00 2001
From: jduncanator <jduncanator@users.noreply.github.com>
Date: Mon, 29 Oct 2018 09:31:13 +1100
Subject: [PATCH] Timing: Optimize Timestamp Aquisition (#479)

* Timing: Optimize Timestamp Aquisition

Currently, we make use of Environment.TickCount in a number of places. This has some downsides, mainly being that the TickCount is a signed 32-bit integer, and has an effective limit of ~25 days before overflowing and wrapping around. Due to the signed-ness of the value, this also caused issues with negative numbers. This resolves these issues by using a 64-bit tick count obtained from Performance Counters (via the Stopwatch class). This has a beneficial side effect of being significantly more accurate than the TickCount.

* Timing: Rename ElapsedTicks to ElapsedMilliseconds and expose TicksPerX

* Timing: Some style changes

* Timing: Align static variable initialization
---
 ChocolArm64/ATranslatorCache.cs               | 24 +++----
 Ryujinx.Common/PerformanceCounter.cs          | 65 +++++++++++++++++++
 .../Gal/OpenGL/OGLCachedResource.cs           | 21 ++----
 Ryujinx.Graphics/NvGpuEngine3d.cs             |  3 +-
 Ryujinx.HLE/HOS/Kernel/KCoreContext.cs        |  3 +-
 Ryujinx.HLE/Hid/Hid.cs                        | 35 +++++-----
 6 files changed, 102 insertions(+), 49 deletions(-)
 create mode 100644 Ryujinx.Common/PerformanceCounter.cs

diff --git a/ChocolArm64/ATranslatorCache.cs b/ChocolArm64/ATranslatorCache.cs
index 3e3c5ab65..2d6af90b4 100644
--- a/ChocolArm64/ATranslatorCache.cs
+++ b/ChocolArm64/ATranslatorCache.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Runtime.CompilerServices;
 using System.Threading;
 
@@ -27,7 +28,7 @@ namespace ChocolArm64
 
             public int Size { get; private set; }
 
-            public int Timestamp { get; private set; }
+            public long Timestamp { get; private set; }
 
             public CacheBucket(ATranslatedSub Subroutine, LinkedListNode<long> Node, int Size)
             {
@@ -41,7 +42,7 @@ namespace ChocolArm64
             {
                 this.Node = Node;
 
-                Timestamp = Environment.TickCount;
+                Timestamp = GetTimestamp();
             }
         }
 
@@ -122,7 +123,7 @@ namespace ChocolArm64
 
         private void ClearCacheIfNeeded()
         {
-            int Timestamp = Environment.TickCount;
+            long Timestamp = GetTimestamp();
 
             while (TotalSize > MaxTotalSize)
             {
@@ -137,9 +138,9 @@ namespace ChocolArm64
 
                     CacheBucket Bucket = Cache[Node.Value];
 
-                    int TimeDelta = RingDelta(Bucket.Timestamp, Timestamp);
+                    long TimeDelta = Bucket.Timestamp - Timestamp;
 
-                    if ((uint)TimeDelta <= (uint)MinTimeDelta)
+                    if (TimeDelta <= MinTimeDelta)
                     {
                         break;
                     }
@@ -154,16 +155,11 @@ namespace ChocolArm64
             }
         }
 
-        private static int RingDelta(int Old, int New)
+        private static long GetTimestamp()
         {
-            if ((uint)New < (uint)Old)
-            {
-                return New + (~Old + 1);
-            }
-            else
-            {
-                return New - Old;
-            }
+            long timestamp = Stopwatch.GetTimestamp();
+
+            return timestamp / (Stopwatch.Frequency / 1000);
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Common/PerformanceCounter.cs b/Ryujinx.Common/PerformanceCounter.cs
new file mode 100644
index 000000000..4c8ae6a75
--- /dev/null
+++ b/Ryujinx.Common/PerformanceCounter.cs
@@ -0,0 +1,65 @@
+using System.Diagnostics;
+
+namespace Ryujinx.Common
+{
+    public static class PerformanceCounter
+    {
+        /// <summary>
+        /// Represents the number of ticks in 1 day.
+        /// </summary>
+        public static long TicksPerDay { get; }
+
+        /// <summary>
+        /// Represents the number of ticks in 1 hour.
+        /// </summary>
+        public static long TicksPerHour { get; }
+
+        /// <summary>
+        /// Represents the number of ticks in 1 minute.
+        /// </summary>
+        public static long TicksPerMinute { get; }
+
+        /// <summary>
+        /// Represents the number of ticks in 1 second.
+        /// </summary>
+        public static long TicksPerSecond { get; }
+
+        /// <summary>
+        /// Represents the number of ticks in 1 millisecond.
+        /// </summary>
+        public static long TicksPerMillisecond { get; }
+
+        /// <summary>
+        /// Gets the number of milliseconds elapsed since the system started.
+        /// </summary>
+        public static long ElapsedTicks
+        {
+            get
+            {
+                return Stopwatch.GetTimestamp();
+            }
+        }
+
+        /// <summary>
+        /// Gets the number of milliseconds elapsed since the system started.
+        /// </summary>
+        public static long ElapsedMilliseconds
+        {
+            get
+            {
+                long timestamp = Stopwatch.GetTimestamp();
+
+                return timestamp / TicksPerMillisecond;
+            }
+        }
+
+        static PerformanceCounter()
+        {
+            TicksPerMillisecond = Stopwatch.Frequency / 1000;
+            TicksPerSecond      = Stopwatch.Frequency;
+            TicksPerMinute      = TicksPerSecond * 60;
+            TicksPerHour        = TicksPerMinute * 60;
+            TicksPerDay         = TicksPerHour * 24;
+        }
+    }
+}
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs
index 839915eae..a65ebcbbc 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
 using System;
 using System.Collections.Generic;
 
@@ -18,7 +19,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
             public long DataSize { get; private set; }
 
-            public int Timestamp { get; private set; }
+            public long Timestamp { get; private set; }
 
             public CacheBucket(T Value, long DataSize, LinkedListNode<long> Node)
             {
@@ -26,7 +27,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 this.DataSize = DataSize;
                 this.Node     = Node;
 
-                Timestamp = Environment.TickCount;
+                Timestamp = PerformanceCounter.ElapsedMilliseconds;
             }
         }
 
@@ -141,7 +142,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         private void ClearCacheIfNeeded()
         {
-            int Timestamp = Environment.TickCount;
+            long Timestamp = PerformanceCounter.ElapsedMilliseconds;
 
             int Count = 0;
 
@@ -156,7 +157,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
                 CacheBucket Bucket = Cache[Node.Value];
 
-                int TimeDelta = RingDelta(Bucket.Timestamp, Timestamp);
+                long TimeDelta = Bucket.Timestamp - Timestamp;
 
                 if ((uint)TimeDelta <= (uint)MaxTimeDelta)
                 {
@@ -170,17 +171,5 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 DeleteValueCallback(Bucket.Value);
             }
         }
-
-        private int RingDelta(int Old, int New)
-        {
-            if ((uint)New < (uint)Old)
-            {
-                return New + (~Old + 1);
-            }
-            else
-            {
-                return New - Old;
-            }
-        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/NvGpuEngine3d.cs b/Ryujinx.Graphics/NvGpuEngine3d.cs
index 7d2b2b430..618d7d9fa 100644
--- a/Ryujinx.Graphics/NvGpuEngine3d.cs
+++ b/Ryujinx.Graphics/NvGpuEngine3d.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
 using Ryujinx.Graphics.Gal;
 using Ryujinx.Graphics.Memory;
 using Ryujinx.Graphics.Texture;
@@ -851,7 +852,7 @@ namespace Ryujinx.Graphics
                     //TODO: Implement counters.
                     long Counter = 1;
 
-                    long Timestamp = (uint)Environment.TickCount;
+                    long Timestamp = PerformanceCounter.ElapsedMilliseconds;
 
                     Timestamp = (long)(Timestamp * 615384.615385);
 
diff --git a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs
index 51f27e2a1..02354e16a 100644
--- a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs
+++ b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
 using System;
 
 namespace Ryujinx.HLE.HOS.Kernel
@@ -25,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel
 
             if (Thread != null)
             {
-                Thread.LastScheduledTicks = (uint)Environment.TickCount;
+                Thread.LastScheduledTicks = PerformanceCounter.ElapsedMilliseconds;
             }
 
             if (SelectedThread != CurrentThread)
diff --git a/Ryujinx.HLE/Hid/Hid.cs b/Ryujinx.HLE/Hid/Hid.cs
index 66b38db59..21580223e 100644
--- a/Ryujinx.HLE/Hid/Hid.cs
+++ b/Ryujinx.HLE/Hid/Hid.cs
@@ -1,4 +1,5 @@
-using Ryujinx.HLE.HOS;
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS;
 using System;
 
 namespace Ryujinx.HLE.Input
@@ -98,12 +99,12 @@ namespace Ryujinx.HLE.Input
 
             HidControllerColorDesc SplitColorDesc = 0;
 
-            Device.Memory.WriteInt32(BaseControllerOffset + 0x0,  (int)Type);
+            Device.Memory.WriteInt32(BaseControllerOffset + 0x00, (int)Type);
 
-            Device.Memory.WriteInt32(BaseControllerOffset + 0x4,  IsHalf ? 1 : 0);
+            Device.Memory.WriteInt32(BaseControllerOffset + 0x04, IsHalf ? 1 : 0);
 
-            Device.Memory.WriteInt32(BaseControllerOffset + 0x8,  (int)SingleColorDesc);
-            Device.Memory.WriteInt32(BaseControllerOffset + 0xc,  (int)SingleColorBody);
+            Device.Memory.WriteInt32(BaseControllerOffset + 0x08, (int)SingleColorDesc);
+            Device.Memory.WriteInt32(BaseControllerOffset + 0x0c, (int)SingleColorBody);
             Device.Memory.WriteInt32(BaseControllerOffset + 0x10, (int)SingleColorButtons);
             Device.Memory.WriteInt32(BaseControllerOffset + 0x14, (int)SplitColorDesc);
 
@@ -186,8 +187,8 @@ namespace Ryujinx.HLE.Input
 
             long Timestamp = GetTimestamp();
 
-            Device.Memory.WriteInt64(ControllerOffset + 0x0,  Timestamp);
-            Device.Memory.WriteInt64(ControllerOffset + 0x8,  HidEntryCount);
+            Device.Memory.WriteInt64(ControllerOffset + 0x00, Timestamp);
+            Device.Memory.WriteInt64(ControllerOffset + 0x08, HidEntryCount);
             Device.Memory.WriteInt64(ControllerOffset + 0x10, CurrEntry);
             Device.Memory.WriteInt64(ControllerOffset + 0x18, HidEntryCount - 1);
 
@@ -199,8 +200,8 @@ namespace Ryujinx.HLE.Input
 
             long SampleCounter = Device.Memory.ReadInt64(LastEntryOffset) + 1;
 
-            Device.Memory.WriteInt64(ControllerOffset + 0x0, SampleCounter);
-            Device.Memory.WriteInt64(ControllerOffset + 0x8, SampleCounter);
+            Device.Memory.WriteInt64(ControllerOffset + 0x00, SampleCounter);
+            Device.Memory.WriteInt64(ControllerOffset + 0x08, SampleCounter);
 
             Device.Memory.WriteInt64(ControllerOffset + 0x10, (uint)Buttons);
 
@@ -225,8 +226,8 @@ namespace Ryujinx.HLE.Input
 
             long Timestamp = GetTimestamp();
 
-            Device.Memory.WriteInt64(TouchScreenOffset + 0x0,  Timestamp);
-            Device.Memory.WriteInt64(TouchScreenOffset + 0x8,  HidEntryCount);
+            Device.Memory.WriteInt64(TouchScreenOffset + 0x00, Timestamp);
+            Device.Memory.WriteInt64(TouchScreenOffset + 0x08, HidEntryCount);
             Device.Memory.WriteInt64(TouchScreenOffset + 0x10, CurrEntry);
             Device.Memory.WriteInt64(TouchScreenOffset + 0x18, HidEntryCount - 1);
             Device.Memory.WriteInt64(TouchScreenOffset + 0x20, Timestamp);
@@ -239,8 +240,8 @@ namespace Ryujinx.HLE.Input
 
             TouchEntryOffset += CurrEntry * HidTouchEntrySize;
 
-            Device.Memory.WriteInt64(TouchEntryOffset + 0x0, SampleCounter);
-            Device.Memory.WriteInt64(TouchEntryOffset + 0x8, Points.Length);
+            Device.Memory.WriteInt64(TouchEntryOffset + 0x00, SampleCounter);
+            Device.Memory.WriteInt64(TouchEntryOffset + 0x08, Points.Length);
 
             TouchEntryOffset += HidTouchEntryHeaderSize;
 
@@ -250,9 +251,9 @@ namespace Ryujinx.HLE.Input
 
             foreach (HidTouchPoint Point in Points)
             {
-                Device.Memory.WriteInt64(TouchEntryOffset + 0x0,  Timestamp);
-                Device.Memory.WriteInt32(TouchEntryOffset + 0x8,  Padding);
-                Device.Memory.WriteInt32(TouchEntryOffset + 0xc,  Index++);
+                Device.Memory.WriteInt64(TouchEntryOffset + 0x00, Timestamp);
+                Device.Memory.WriteInt32(TouchEntryOffset + 0x08, Padding);
+                Device.Memory.WriteInt32(TouchEntryOffset + 0x0c, Index++);
                 Device.Memory.WriteInt32(TouchEntryOffset + 0x10, Point.X);
                 Device.Memory.WriteInt32(TouchEntryOffset + 0x14, Point.Y);
                 Device.Memory.WriteInt32(TouchEntryOffset + 0x18, Point.DiameterX);
@@ -266,7 +267,7 @@ namespace Ryujinx.HLE.Input
 
         private static long GetTimestamp()
         {
-            return (long)((ulong)Environment.TickCount * 19_200);
+            return PerformanceCounter.ElapsedMilliseconds * 19200;
         }
     }
 }