aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Desktop/FrameTimeGraph.cs
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-06 23:39:36 +0200
committergodosa <godosa@godosa.eu>2026-10-06 23:39:36 +0200
commit39066900773e7857faf2d02e7ed51b71d219d97d (patch)
treeffab8e4ddd971626776c3b1f4ce1da2c186c2187 /src/Godosa.Desktop/FrameTimeGraph.cs
downloadgodosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.tar.gz
godosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.zip
godosa-engine: initial public history
Diffstat (limited to 'src/Godosa.Desktop/FrameTimeGraph.cs')
-rw-r--r--src/Godosa.Desktop/FrameTimeGraph.cs65
1 files changed, 65 insertions, 0 deletions
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;
+
+/// <summary>
+/// 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.
+/// </summary>
+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));
+ }
+}