From 39066900773e7857faf2d02e7ed51b71d219d97d Mon Sep 17 00:00:00 2001 From: godosa Date: Tue, 6 Oct 2026 23:39:36 +0200 Subject: godosa-engine: initial public history --- src/Godosa.Desktop/FrameTimeGraph.cs | 65 ++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/Godosa.Desktop/FrameTimeGraph.cs (limited to 'src/Godosa.Desktop/FrameTimeGraph.cs') diff --git a/src/Godosa.Desktop/FrameTimeGraph.cs b/src/Godosa.Desktop/FrameTimeGraph.cs new file mode 100644 index 0000000..7e59a17 --- /dev/null +++ b/src/Godosa.Desktop/FrameTimeGraph.cs @@ -0,0 +1,65 @@ +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)); + } +} -- cgit