Replace scheduler-quantized software sleeps with a reusable Windows high-resolution deadline timer, expose pacing in the frame profiler, and make shutdown wake every persistent mesh worker without losing the shared signal. Preserve retail alpha order while using a stable radix, skip duplicate deferred-alpha SSBO packing, pack light sets, cache static selection descriptors, and retire historical material groups at the whole-frame boundary. The fixed dense-Caul sample improved from roughly 9-12 ms CPU to 5.3-6.2 ms without reducing visual quality. Release build succeeds with zero warnings and all 6,300 tests pass with five intentional skips. Three independent retail, architecture, and adversarial reviews are clean; the post-review connected route remains pending because local ACE is offline. Co-authored-by: OpenAI Codex <codex@openai.com>
169 lines
5.9 KiB
C#
169 lines
5.9 KiB
C#
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Low-CPU, sub-millisecond-capable deadline wait for the Windows render loop.
|
|
/// One auto-reset timer is reused for the complete window lifetime.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// A normal <see cref="Thread.Sleep(int)"/> is quantized by the Windows scheduler.
|
|
/// At a 165 Hz presentation target, its variable overshoot turned a requested
|
|
/// 6.1 ms frame into a 10-16 ms frame. A high-resolution waitable timer keeps
|
|
/// the render thread blocked in the kernel without that coarse sleep quantum.
|
|
/// </para>
|
|
/// <para>
|
|
/// <c>CREATE_WAITABLE_TIMER_HIGH_RESOLUTION</c> is available on Windows 10
|
|
/// version 1803 and newer, which is the client's supported desktop baseline.
|
|
/// Microsoft documents relative due times as negative 100-nanosecond units.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal sealed partial class WindowsHighResolutionFramePacingWaiter :
|
|
IFramePacingWaiter,
|
|
IDisposable
|
|
{
|
|
private const uint CreateWaitableTimerHighResolution = 0x00000002;
|
|
private const uint TimerModifyState = 0x00000002;
|
|
private const uint Synchronize = 0x00100000;
|
|
private const uint WaitObject0 = 0x00000000;
|
|
private const uint WaitTimeout = 0x00000102;
|
|
private const uint WaitFailed = 0xFFFFFFFF;
|
|
private const long HundredNanosecondsPerSecond = 10_000_000;
|
|
|
|
private readonly SafeWaitHandle _timer;
|
|
private bool _disposed;
|
|
|
|
private WindowsHighResolutionFramePacingWaiter(SafeWaitHandle timer)
|
|
=> _timer = timer;
|
|
|
|
public static WindowsHighResolutionFramePacingWaiter Create()
|
|
{
|
|
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
|
{
|
|
throw new PlatformNotSupportedException(
|
|
"acdream software frame pacing requires Windows 10 version 1803 or newer.");
|
|
}
|
|
|
|
SafeWaitHandle timer = CreateWaitableTimerExW(
|
|
0,
|
|
0,
|
|
CreateWaitableTimerHighResolution,
|
|
TimerModifyState | Synchronize);
|
|
if (timer.IsInvalid)
|
|
{
|
|
int error = Marshal.GetLastPInvokeError();
|
|
timer.Dispose();
|
|
throw new Win32Exception(error, "Could not create the high-resolution frame timer.");
|
|
}
|
|
|
|
return new WindowsHighResolutionFramePacingWaiter(timer);
|
|
}
|
|
|
|
public void Wait(long durationTicks, long clockFrequency)
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
if (durationTicks <= 0 || clockFrequency <= 0)
|
|
return;
|
|
|
|
long hundredNanoseconds = ConvertTicksToHundredNanoseconds(
|
|
durationTicks,
|
|
clockFrequency);
|
|
long relativeDueTime = -hundredNanoseconds;
|
|
|
|
if (!SetWaitableTimerEx(
|
|
_timer,
|
|
in relativeDueTime,
|
|
periodMilliseconds: 0,
|
|
completionRoutine: 0,
|
|
completionArgument: 0,
|
|
wakeContext: 0,
|
|
tolerableDelayMilliseconds: 0))
|
|
{
|
|
throw new Win32Exception(
|
|
Marshal.GetLastPInvokeError(),
|
|
"Could not arm the high-resolution frame timer.");
|
|
}
|
|
|
|
uint result = WaitForSingleObject(
|
|
_timer,
|
|
ComputeFailureTimeoutMilliseconds(hundredNanoseconds));
|
|
if (result == WaitObject0)
|
|
return;
|
|
if (result == WaitTimeout)
|
|
{
|
|
throw new TimeoutException(
|
|
"The high-resolution frame timer did not signal before its safety timeout.");
|
|
}
|
|
|
|
int waitError = result == WaitFailed
|
|
? Marshal.GetLastPInvokeError()
|
|
: unchecked((int)result);
|
|
throw new Win32Exception(waitError, "Waiting on the high-resolution frame timer failed.");
|
|
}
|
|
|
|
internal static long ConvertTicksToHundredNanoseconds(
|
|
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 / HundredNanosecondsPerSecond)
|
|
return long.MaxValue;
|
|
|
|
long wholeIntervals = wholeSeconds * HundredNanosecondsPerSecond;
|
|
long fractionalIntervals = (long)Math.Ceiling(
|
|
remainder * (double)HundredNanosecondsPerSecond / clockFrequency);
|
|
if (wholeIntervals > long.MaxValue - fractionalIntervals)
|
|
return long.MaxValue;
|
|
|
|
return Math.Max(1, wholeIntervals + fractionalIntervals);
|
|
}
|
|
|
|
private static uint ComputeFailureTimeoutMilliseconds(long hundredNanoseconds)
|
|
{
|
|
double dueMilliseconds = hundredNanoseconds / 10_000d;
|
|
double timeoutMilliseconds = Math.Ceiling(dueMilliseconds) + 1_000d;
|
|
return timeoutMilliseconds >= uint.MaxValue - 1d
|
|
? uint.MaxValue - 1
|
|
: Math.Max(1u, (uint)timeoutMilliseconds);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
_disposed = true;
|
|
_timer.Dispose();
|
|
}
|
|
|
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
|
private static partial SafeWaitHandle CreateWaitableTimerExW(
|
|
nint timerAttributes,
|
|
nint timerName,
|
|
uint flags,
|
|
uint desiredAccess);
|
|
|
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static partial bool SetWaitableTimerEx(
|
|
SafeWaitHandle timer,
|
|
in long dueTime,
|
|
int periodMilliseconds,
|
|
nint completionRoutine,
|
|
nint completionArgument,
|
|
nint wakeContext,
|
|
uint tolerableDelayMilliseconds);
|
|
|
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
|
private static partial uint WaitForSingleObject(
|
|
SafeWaitHandle handle,
|
|
uint milliseconds);
|
|
}
|