diff --git a/src/Avalonia.X11/X11HighResRenderTimer.cs b/src/Avalonia.X11/X11HighResRenderTimer.cs new file mode 100644 index 0000000000..8fecb9b23c --- /dev/null +++ b/src/Avalonia.X11/X11HighResRenderTimer.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Avalonia.Controls.Platform; +using Avalonia.FreeDesktop; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.OpenGL; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.X11.Glx; +using Avalonia.X11.NativeDialogs; +using static Avalonia.X11.XLib; + +namespace Avalonia.X11 +{ + internal class LinuxHighResolutionTimer : IDisposable + { + [DllImport("libc", SetLastError = true)] + static extern int nanosleep(ref TimeSpec duration, ref TimeSpec remaining); + + [DllImport("libc", SetLastError = true)] + static extern int clock_gettime(uint clk_id, ref TimeSpec tp); + + const uint CLOCK_MONOTONIC = 1; + + struct TimeSpec + { + public long tv_sec; + public long tv_nsec; + + public TimeSpec(int tv_sec, long tv_nsec) : this() + { + this.tv_sec = tv_sec; + this.tv_nsec = tv_nsec; + } + + public TimeSpan ToTimeSpan() + => TimeSpan.FromSeconds(tv_sec) + TimeSpan.FromTicks(tv_nsec / 100); + + public static TimeSpec ConvertToTimeSpec(TimeSpan timeSpan) + => new TimeSpec(timeSpan.Seconds, timeSpan.Ticks * 100); + }; + + TimeSpec GetElapsedTimeSpec(ref TimeSpec start, ref TimeSpec stop) + { + var elapsed_time = new TimeSpec(); + if ((stop.tv_nsec - start.tv_nsec) < 0) + { + elapsed_time.tv_sec = stop.tv_sec - start.tv_sec - 1; + elapsed_time.tv_nsec = stop.tv_nsec - start.tv_nsec + 1000000000; + } + else + { + elapsed_time.tv_sec = stop.tv_sec - start.tv_sec; + elapsed_time.tv_nsec = stop.tv_nsec - start.tv_nsec; + } + return elapsed_time; + } + + public LinuxHighResolutionTimer(double seconds) + { + totalInterval = TimeSpan.FromSeconds(seconds); + } + + private volatile bool shouldStop = false; + private TimeSpan elapsedTotal = TimeSpan.Zero; + private IDisposable _diposable1; + private TimeSpan totalInterval; + + private void TickTock(Action observer) + { + var start = new TimeSpec(); + var frameStop = new TimeSpec(); + var sleepStop = new TimeSpec(); + var remaining = new TimeSpec(); + + TimeSpan frameTime, totalTime; + + while (!shouldStop) + { + clock_gettime(CLOCK_MONOTONIC, ref start); + + observer(elapsedTotal); + + clock_gettime(CLOCK_MONOTONIC, ref frameStop); + + frameTime = GetElapsedTimeSpec(ref start, ref frameStop).ToTimeSpan(); + + var calc = (totalInterval - frameTime); + + if (calc < TimeSpan.Zero) + calc = totalInterval; + + var finDur = TimeSpec.ConvertToTimeSpec(calc); + + nanosleep(ref finDur, ref remaining); + + clock_gettime(CLOCK_MONOTONIC, ref sleepStop); + + totalTime = GetElapsedTimeSpec(ref start, ref sleepStop).ToTimeSpan(); + + elapsedTotal += totalTime; + } + } + + public void Dispose() + { + shouldStop = true; + _diposable1?.Dispose(); + } + + public IDisposable Subscribe(Action observer) + { + _diposable1 = Task.Run(() => TickTock(observer)); + return this; + } + } + + + internal class X11HighResRenderTimer : DefaultRenderTimer + { + private double intervalMills; + + public X11HighResRenderTimer(int framesPerSecond) : base(framesPerSecond) + { + this.intervalMills = 1d / (double)framesPerSecond; + } + + + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override string ToString() + { + return base.ToString(); + } + + protected override IDisposable StartCore(Action tick) + { + return new LinuxHighResolutionTimer(intervalMills).Subscribe(tick); + } + } +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 1d2290236c..455add923f 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.FreeDesktop; @@ -42,7 +43,7 @@ namespace Avalonia.X11 AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(this) .Bind().ToConstant(new X11PlatformThreading(this)) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new X11HighResRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) .Bind().ToFunc(() => KeyboardDevice) @@ -52,7 +53,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new X11IconLoader(Info)) .Bind().ToConstant(new GtkSystemDialog()) .Bind().ToConstant(new LinuxMountedVolumeInfoProvider()); - + X11Screens = Avalonia.X11.X11Screens.Init(this); Screens = new X11Screens(X11Screens); if (Info.XInputVersion != null) @@ -70,7 +71,7 @@ namespace Avalonia.X11 GlxGlPlatformFeature.TryInitialize(Info); } - + } public IntPtr DeferredDisplay { get; set; }