using System; using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Godosa.Core.Diagnostics; namespace Godosa.Desktop; /// /// Frame-time bar graph (game-loop spec §5): one 1-px bar per frame in the /// stats window, newest at the right, 0–50 ms scale, dashed guides at 16.7 ms /// (60 fps) and 33.3 ms (30 fps). Bars are colored relative to the window /// median (green ≤ 1.5×, yellow ≤ 2×, red = hitch) so ordinary vsync jitter /// stays green whatever the refresh rate. Drawn by Avalonia, not GL, so it /// doesn't perturb the GL frames it measures. Presentation only. /// public sealed class FrameTimeGraph : Control { private const float ScaleMs = 50f; private const float Ms60 = 1000f / 60f; private const float Ms30 = 1000f / 30f; private static readonly IBrush Backdrop = new SolidColorBrush(Color.FromArgb(90, 255, 255, 255)); private static readonly IBrush Good = new SolidColorBrush(Color.FromRgb(80, 200, 120)); private static readonly IBrush Slow = new SolidColorBrush(Color.FromRgb(230, 200, 60)); private static readonly IBrush Bad = new SolidColorBrush(Color.FromRgb(230, 70, 60)); private static readonly IPen Guide = new Pen(new SolidColorBrush(Color.FromArgb(170, 255, 255, 255)), 1, DashStyle.Dash); private readonly FrameTimeStats _stats; private readonly float[] _buffer; public FrameTimeGraph(FrameTimeStats stats) { _stats = stats; _buffer = new float[stats.Capacity]; Width = stats.Capacity; Height = 60; } public override void Render(DrawingContext context) { double w = Bounds.Width, h = Bounds.Height; context.FillRectangle(Backdrop, new Rect(0, 0, w, h)); int n = _stats.CopyIntervalsMs(_buffer); float median = (float)_stats.Summarize().P50Ms; double x0 = w - n; for (int i = 0; i < n; i++) { float ms = _buffer[i]; double barHeight = Math.Min(ms / ScaleMs, 1f) * h; var brush = ms > 2f * median ? Bad : ms > 1.5f * median ? Slow : Good; context.FillRectangle(brush, new Rect(x0 + i, h - barHeight, 1, barHeight)); } DrawGuide(context, Ms60, w, h); DrawGuide(context, Ms30, w, h); } private static void DrawGuide(DrawingContext context, float ms, double w, double h) { double y = h - ms / ScaleMs * h; context.DrawLine(Guide, new Point(0, y), new Point(w, y)); } }