XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Desktop;

/// <summary>
/// Tick-precision dirty-frame pacing. This measures renderer submissions, never
/// monitor scanout or VSync. An idle desktop presents no frames. After an overrun,
/// one current frame can run immediately; obsolete deadlines are skipped rather
/// than queued, and the original fractional-tick phase is retained.
/// </summary>
public sealed class FramePacer
{
    private readonly long _frequency;
    private readonly UInt128 _periodNumerator;
    private long _origin;
    private long _lastBegin;
    private long _lastEnd;

    public FramePacer(long timestampFrequency, int targetMilliHz)
    {
        if (timestampFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(timestampFrequency));
        if (targetMilliHz <= 0) throw new ArgumentOutOfRangeException(nameof(targetMilliHz));
        _periodNumerator = (UInt128)(ulong)timestampFrequency * 1000;
        if (_periodNumerator < (uint)targetMilliHz)
            throw new ArgumentOutOfRangeException(nameof(targetMilliHz), "Target exceeds timestamp tick precision");
        _frequency = timestampFrequency;
        TargetMilliHz = targetMilliHz;
    }

    public int TargetMilliHz { get; }
    public long NextDeadlineTicks { get; private set; }
    public long FramesRendered { get; private set; }
    public long Overruns { get; private set; }
    public long LastRenderDurationTicks { get; private set; }

    /// <summary>Observed Rendered call cadence, including idle time; not display refresh.</summary>
    public double ActualRendererFps => FramesRendered < 2 || _lastBegin <= _origin ? 0 :
        (FramesRendered - 1) * (double)_frequency / (_lastBegin - _origin);

    public bool ShouldRender(long nowTicks, bool dirty)
    {
        if (nowTicks < 0) throw new ArgumentOutOfRangeException(nameof(nowTicks));
        return dirty && (FramesRendered == 0 || nowTicks >= NextDeadlineTicks);
    }

    public void Rendered(long beginTicks, long endTicks)
    {
        if (beginTicks < 0) throw new ArgumentOutOfRangeException(nameof(beginTicks));
        if (endTicks < beginTicks) throw new ArgumentOutOfRangeException(nameof(endTicks));
        if (FramesRendered != 0 && beginTicks < _lastEnd)
            throw new ArgumentOutOfRangeException(nameof(beginTicks), "Render timestamps must be monotonic and nonoverlapping");
        if (FramesRendered == 0) _origin = beginTicks;
        _lastBegin = beginTicks;
        _lastEnd = endTicks;
        LastRenderDurationTicks = endTicks - beginTicks;
        if ((UInt128)(ulong)LastRenderDurationTicks * (uint)TargetMilliHz > _periodNumerator && Overruns < long.MaxValue)
            Overruns++;
        if (FramesRendered < long.MaxValue) FramesRendered++;

        UInt128 phaseAtBegin = (UInt128)(ulong)(beginTicks - _origin) * (uint)TargetMilliHz / _periodNumerator;
        UInt128 phaseAtEnd = (UInt128)(ulong)(endTicks - _origin) * (uint)TargetMilliHz / _periodNumerator;
        UInt128 nextPhase = phaseAtBegin + 1;
        if (phaseAtEnd > nextPhase) nextPhase = phaseAtEnd;
        UInt128 numerator = nextPhase * _periodNumerator;
        UInt128 elapsedDeadline = (numerator + (uint)TargetMilliHz - 1) / (uint)TargetMilliHz;
        UInt128 absoluteDeadline = (ulong)_origin + elapsedDeadline;
        NextDeadlineTicks = absoluteDeadline > long.MaxValue ? long.MaxValue : (long)absoluteDeadline;
    }
}