151 lines
4.6 KiB
C#
151 lines
4.6 KiB
C#
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Low-CPU Linux frame wait using an absolute CLOCK_MONOTONIC deadline.
|
|
/// EINTR resumes against the original deadline, so signals cannot shorten or
|
|
/// cumulatively extend a normal presentation wait.
|
|
/// </summary>
|
|
internal sealed partial class LinuxMonotonicFramePacingWaiter
|
|
: IFramePacingWaiter,
|
|
IDisposable
|
|
{
|
|
private const int ClockMonotonic = 1;
|
|
private const int TimerAbsolute = 1;
|
|
private const int Interrupted = 4;
|
|
private const long NanosecondsPerSecond = 1_000_000_000L;
|
|
private bool _disposed;
|
|
|
|
private LinuxMonotonicFramePacingWaiter()
|
|
{
|
|
}
|
|
|
|
internal static LinuxMonotonicFramePacingWaiter Create()
|
|
{
|
|
if (!OperatingSystem.IsLinux())
|
|
{
|
|
throw new PlatformNotSupportedException(
|
|
"The monotonic Linux frame waiter requires Linux.");
|
|
}
|
|
|
|
return new LinuxMonotonicFramePacingWaiter();
|
|
}
|
|
|
|
public void Wait(long durationTicks, long clockFrequency)
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
if (durationTicks <= 0 || clockFrequency <= 0)
|
|
return;
|
|
|
|
int result = ClockGetTime(ClockMonotonic, out Timespec now);
|
|
if (result != 0)
|
|
{
|
|
throw new Win32Exception(
|
|
Marshal.GetLastPInvokeError(),
|
|
"Could not read the Linux monotonic clock.");
|
|
}
|
|
|
|
long durationNanoseconds = ConvertTicksToNanoseconds(
|
|
durationTicks,
|
|
clockFrequency);
|
|
Timespec deadline = AddNanoseconds(now, durationNanoseconds);
|
|
do
|
|
{
|
|
result = ClockNanosleep(
|
|
ClockMonotonic,
|
|
TimerAbsolute,
|
|
in deadline,
|
|
0);
|
|
}
|
|
while (result == Interrupted);
|
|
|
|
if (result != 0)
|
|
{
|
|
throw new Win32Exception(
|
|
result,
|
|
"Waiting on the Linux monotonic frame deadline failed.");
|
|
}
|
|
}
|
|
|
|
internal static long ConvertTicksToNanoseconds(
|
|
long durationTicks,
|
|
long clockFrequency)
|
|
{
|
|
if (durationTicks <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(durationTicks));
|
|
if (clockFrequency <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(clockFrequency));
|
|
|
|
long wholeSeconds = Math.DivRem(
|
|
durationTicks,
|
|
clockFrequency,
|
|
out long remainder);
|
|
if (wholeSeconds >= long.MaxValue / NanosecondsPerSecond)
|
|
return long.MaxValue;
|
|
|
|
long wholeNanoseconds = wholeSeconds * NanosecondsPerSecond;
|
|
long fractionalNanoseconds = checked((long)Math.Ceiling(
|
|
remainder * (double)NanosecondsPerSecond / clockFrequency));
|
|
if (wholeNanoseconds > long.MaxValue - fractionalNanoseconds)
|
|
return long.MaxValue;
|
|
|
|
return Math.Max(1L, wholeNanoseconds + fractionalNanoseconds);
|
|
}
|
|
|
|
private static Timespec AddNanoseconds(
|
|
Timespec timestamp,
|
|
long nanoseconds)
|
|
{
|
|
long seconds = nanoseconds / NanosecondsPerSecond;
|
|
long remainder = nanoseconds % NanosecondsPerSecond;
|
|
long resultSeconds = timestamp.Seconds > long.MaxValue - seconds
|
|
? long.MaxValue
|
|
: timestamp.Seconds + seconds;
|
|
if (resultSeconds == long.MaxValue)
|
|
{
|
|
return new Timespec(
|
|
long.MaxValue,
|
|
NanosecondsPerSecond - 1L);
|
|
}
|
|
|
|
long resultNanoseconds = timestamp.Nanoseconds + remainder;
|
|
if (resultNanoseconds >= NanosecondsPerSecond)
|
|
{
|
|
if (resultSeconds == long.MaxValue)
|
|
{
|
|
return new Timespec(
|
|
long.MaxValue,
|
|
NanosecondsPerSecond - 1L);
|
|
}
|
|
|
|
resultSeconds++;
|
|
resultNanoseconds -= NanosecondsPerSecond;
|
|
}
|
|
|
|
return new Timespec(resultSeconds, resultNanoseconds);
|
|
}
|
|
|
|
public void Dispose() => _disposed = true;
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private readonly record struct Timespec(
|
|
long Seconds,
|
|
long Nanoseconds);
|
|
|
|
[LibraryImport(
|
|
"libc",
|
|
EntryPoint = "clock_gettime",
|
|
SetLastError = true)]
|
|
private static partial int ClockGetTime(
|
|
int clockId,
|
|
out Timespec timestamp);
|
|
|
|
[LibraryImport("libc", EntryPoint = "clock_nanosleep")]
|
|
private static partial int ClockNanosleep(
|
|
int clockId,
|
|
int flags,
|
|
in Timespec request,
|
|
nint remainder);
|
|
}
|