refactor(render): extract frame resource preparation

Move ordered resource begin, clear/state setup, live upload/reveal, weather timing, and display pacing behind focused owners while preserving the accepted frame order and retryable shutdown ownership.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 05:23:46 +02:00
parent 733126a272
commit bc6f09f987
8 changed files with 1068 additions and 205 deletions

View file

@ -0,0 +1,141 @@
using AcDream.App.Diagnostics;
using Silk.NET.Maths;
using Silk.NET.Windowing;
namespace AcDream.App.Rendering;
internal interface IDisplayFramePacingSurface
{
bool VSync { get; set; }
int? ActiveMonitorRefreshHz { get; }
}
/// <summary>Narrow Silk window adapter for display pacing.</summary>
internal sealed class SilkDisplayFramePacingSurface : IDisplayFramePacingSurface
{
private readonly IWindow _window;
public SilkDisplayFramePacingSurface(IWindow window)
{
_window = window ?? throw new ArgumentNullException(nameof(window));
}
public bool VSync
{
get => _window.VSync;
set => _window.VSync = value;
}
public int? ActiveMonitorRefreshHz
{
get
{
int? refreshHz = _window.Monitor?.VideoMode.RefreshRate;
return refreshHz is > 0 ? refreshHz : null;
}
}
}
/// <summary>
/// Owns requested VSync, monitor-aware policy resolution, the software pacer,
/// and the post-render pacing event. Window lifecycle and frame preparation
/// call this typed owner rather than duplicating pacing state.
/// </summary>
internal sealed class DisplayFramePacingController : IDisposable
{
private readonly bool _uncappedRendering;
private FrameProfiler? _profiler;
private readonly FramePacingController _pacing;
private IDisplayFramePacingSurface? _surface;
private int? _activeMonitorRefreshHz;
private bool _requestedVSync =
AcDream.UI.Abstractions.Panels.Settings.DisplaySettings.Default.VSync;
public DisplayFramePacingController(
bool uncappedRendering,
FrameProfiler profiler)
: this(uncappedRendering, profiler, new FramePacingController())
{
}
internal DisplayFramePacingController(
bool uncappedRendering,
FrameProfiler profiler,
FramePacingController pacing)
{
_uncappedRendering = uncappedRendering;
_profiler = profiler ?? throw new ArgumentNullException(nameof(profiler));
_pacing = pacing ?? throw new ArgumentNullException(nameof(pacing));
}
internal bool RequestedVSync => _requestedVSync;
internal FramePacingPolicy Policy => _pacing.Policy;
internal bool IsSurfaceBound => _surface is not null;
public FramePacingPolicy InitializeStartup(bool requestedVSync)
{
_requestedVSync = requestedVSync;
FramePacingPolicy policy = Resolve(monitorRefreshHz: null);
_pacing.Apply(policy);
return policy;
}
public void BindSurface(IDisplayFramePacingSurface surface)
{
_surface = surface ?? throw new ArgumentNullException(nameof(surface));
}
public void ApplyPreference(bool requestedVSync)
{
_requestedVSync = requestedVSync;
ApplyResolvedPolicy();
}
public void RefreshActiveMonitor()
{
_activeMonitorRefreshHz = _surface?.ActiveMonitorRefreshHz;
ApplyResolvedPolicy();
}
public void OnWindowMoved(Vector2D<int> _) => RefreshActiveMonitor();
public void OnWindowStateChanged(WindowState _) => RefreshActiveMonitor();
public void OnFrameRendered(double _)
{
FrameProfiler profiler = _profiler
?? throw new ObjectDisposedException(nameof(DisplayFramePacingController));
using var pacingStage = profiler.BeginStage(FrameStage.Pacing);
_pacing.CompleteFrame();
}
private FramePacingPolicy Resolve(int? monitorRefreshHz) =>
FramePacingPolicy.Resolve(
_requestedVSync,
_uncappedRendering,
monitorRefreshHz);
private void ApplyResolvedPolicy()
{
FramePacingPolicy policy = Resolve(_activeMonitorRefreshHz);
_pacing.Apply(policy);
if (_surface is not null && _surface.VSync != policy.UseVSync)
_surface.VSync = policy.UseVSync;
}
public void Dispose()
{
try
{
_pacing.Dispose();
}
finally
{
_surface = null;
_profiler = null;
}
}
}