A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using osu.Framework.Graphics.Containers;
6using osu.Framework.Graphics.Shapes;
7using osu.Framework.Graphics.Sprites;
8using osu.Framework.Statistics;
9using osu.Framework.Timing;
10using osu.Framework.Utils;
11using osuTK;
12using osuTK.Graphics;
13
14namespace osu.Framework.Graphics.Performance
15{
16 internal class FrameTimeDisplay : Container
17 {
18 private readonly SpriteText counter;
19
20 private readonly ThrottledFrameClock clock;
21
22 public bool Counting = true;
23
24 public FrameTimeDisplay(ThrottledFrameClock clock)
25 {
26 this.clock = clock;
27
28 Masking = true;
29 CornerRadius = 5;
30
31 AddRange(new Drawable[]
32 {
33 new Box
34 {
35 RelativeSizeAxes = Axes.Both,
36 Colour = Color4.Black,
37 Alpha = 0.75f
38 },
39 counter = new CounterText
40 {
41 Anchor = Anchor.TopRight,
42 Origin = Anchor.TopRight,
43 Spacing = new Vector2(-1, 0),
44 Text = @"...",
45 }
46 });
47 }
48
49 private float aimWidth;
50
51 private double displayFps;
52
53 private double rollingElapsed;
54
55 private int framesSinceLastUpdate;
56
57 private double elapsedSinceLastUpdate;
58 private double lastUpdateLocalTime;
59 private double lastFrameFramesPerSecond;
60
61 private const int updates_per_second = 10;
62
63 protected override void Update()
64 {
65 base.Update();
66
67 if (!Precision.AlmostEquals(counter.DrawWidth, aimWidth))
68 {
69 ClearTransforms();
70
71 if (aimWidth == 0)
72 Size = counter.DrawSize;
73 else if (Precision.AlmostBigger(counter.DrawWidth, aimWidth))
74 this.ResizeTo(counter.DrawSize, 200, Easing.OutQuint);
75 else
76 this.Delay(500).ResizeTo(counter.DrawSize, 200, Easing.InOutSine);
77
78 aimWidth = counter.DrawWidth;
79 }
80
81 if (Clock.CurrentTime - lastUpdateLocalTime > 1000.0 / updates_per_second)
82 updateDisplay();
83 }
84
85 private void updateDisplay()
86 {
87 double dampRate = Math.Max(Clock.CurrentTime - lastUpdateLocalTime, 0) / 1000;
88
89 displayFps = Interpolation.Damp(displayFps, lastFrameFramesPerSecond, 0.01, dampRate);
90
91 if (framesSinceLastUpdate > 0)
92 {
93 rollingElapsed = Interpolation.Damp(rollingElapsed, elapsedSinceLastUpdate / framesSinceLastUpdate, 0.01, dampRate);
94 }
95
96 lastUpdateLocalTime = Clock.CurrentTime;
97
98 framesSinceLastUpdate = 0;
99 elapsedSinceLastUpdate = 0;
100
101 counter.Text = $"{displayFps:0}fps ({rollingElapsed:0.00}ms)"
102 + (clock.Throttling ? $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞").PadLeft(4)}hz" : string.Empty);
103 }
104
105 private class CounterText : SpriteText
106 {
107 public CounterText()
108 {
109 Font = FrameworkFont.Regular.With(fixedWidth: true);
110 }
111
112 protected override char[] FixedWidthExcludeCharacters { get; } = { ',', '.', ' ' };
113 }
114
115 public void NewFrame(FrameStatistics frame)
116 {
117 if (!Counting) return;
118
119 foreach (var pair in frame.CollectedTimes)
120 {
121 if (pair.Key != PerformanceCollectionType.Sleep)
122 elapsedSinceLastUpdate += pair.Value;
123 }
124
125 framesSinceLastUpdate++;
126 lastFrameFramesPerSecond = frame.FramesPerSecond;
127 }
128 }
129}