1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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));
}
}
|