From 0c3d43026d956be5b9e6ba8ed0534eaf99a0f378 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 02:53:29 +0900 Subject: [PATCH 01/14] Add initial structure for fps counter --- .../UserInterface/TestSceneFPSCounter.cs | 51 ++++++ osu.Game/Graphics/UserInterface/FPSCounter.cs | 149 ++++++++++++++++++ .../UserInterface/FPSCounterTooltip.cs | 96 +++++++++++ 3 files changed, 296 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs create mode 100644 osu.Game/Graphics/UserInterface/FPSCounter.cs create mode 100644 osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs new file mode 100644 index 0000000000..d78707045b --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneFPSCounter : OsuTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create display", () => + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new FPSCounter(), + new FPSCounter { Scale = new Vector2(2) }, + new FPSCounter { Scale = new Vector2(4) }, + } + }, + }; + }); + } + + [Test] + public void TestBasic() + { + } + } +} diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs new file mode 100644 index 0000000000..222c30c1dc --- /dev/null +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -0,0 +1,149 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Framework.Platform; +using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class FPSCounter : CompositeDrawable, IHasCustomTooltip + { + private RollingCounter msCounter = null!; + private RollingCounter fpsCounter = null!; + + private Container mainContent = null!; + + public FPSCounter() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + mainContent = new Container + { + Alpha = 0, + Size = new Vector2(30), + Children = new Drawable[] + { + msCounter = new FrameTimeCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colours.Orange2, + }, + fpsCounter = new FramesPerSecondCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = 11, + Scale = new Vector2(0.8f), + Colour = colours.Lime3, + } + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + displayTemporarily(); + } + + private bool isDisplayed; + + private ScheduledDelegate? fadeOutDelegate; + + private void displayTemporarily() + { + if (!isDisplayed) + mainContent.FadeTo(1, 300, Easing.OutQuint); + + fadeOutDelegate?.Cancel(); + fadeOutDelegate = Scheduler.AddDelayed(() => + { + mainContent.FadeTo(0, 1000, Easing.In); + isDisplayed = false; + }, 2000); + } + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + protected override void Update() + { + base.Update(); + + // TODO: this is wrong (elapsed clock time, not actual run time). + double newFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; + double newFps = gameHost.DrawThread.Clock.FramesPerSecond; + + bool hasSignificantChanges = + Math.Abs(msCounter.Current.Value - newFrameTime) > 5 || + Math.Abs(fpsCounter.Current.Value - newFps) > 10; + + if (hasSignificantChanges) + displayTemporarily(); + + msCounter.Current.Value = newFrameTime; + fpsCounter.Current.Value = newFps; + } + + public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); + + public object TooltipContent => this; + + public class FramesPerSecondCounter : RollingCounter + { + protected override double RollingDuration => 400; + + protected override OsuSpriteText CreateSpriteText() + { + return new OsuSpriteText + { + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-2), + }; + } + + protected override LocalisableString FormatCount(double count) + { + return $"{count:#,0}fps"; + } + } + + public class FrameTimeCounter : RollingCounter + { + protected override double RollingDuration => 1000; + + protected override OsuSpriteText CreateSpriteText() + { + return new OsuSpriteText + { + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-1), + }; + } + + protected override LocalisableString FormatCount(double count) + { + if (count < 1) + return $"{count:N1}ms"; + + return $"{count:N0}ms"; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs new file mode 100644 index 0000000000..af95fbe556 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class FPSCounterTooltip : CompositeDrawable, ITooltip + { + private OsuTextFlowContainer textFlow = null!; + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + + CornerRadius = 15; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + Alpha = 1, + RelativeSizeAxes = Axes.Both, + }, + new OsuTextFlowContainer(cp => + { + cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); + cp.Spacing = new Vector2(-1); + }) + { + AutoSizeAxes = Axes.Both, + TextAnchor = Anchor.TopRight, + Margin = new MarginPadding { Left = 5, Vertical = 10 }, + Text = string.Join('\n', gameHost.Threads.Select(t => t.Name)) + }, + textFlow = new OsuTextFlowContainer(cp => + { + cp.Font = OsuFont.Default.With(fixedWidth: true, weight: FontWeight.Regular); + cp.Spacing = new Vector2(-1); + }) + { + Width = 190, + Margin = new MarginPadding { Left = 35, Right = 10, Vertical = 10 }, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopRight, + }, + }; + } + + private int lastUpdate; + + protected override void Update() + { + int currentSecond = (int)(Clock.CurrentTime / 100); + + if (currentSecond != lastUpdate) + { + lastUpdate = currentSecond; + + textFlow.Clear(); + + foreach (var thread in gameHost.Threads) + { + var clock = thread.Clock; + + string maximum = $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}"; + + textFlow.AddParagraph($"{clock.FramesPerSecond:0}/{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); + } + } + } + + public void SetContent(object content) + { + } + + public void Move(Vector2 pos) + { + Position = pos; + } + } +} From 03e644e548a066efe5c957b6f536b951883fcb8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:28:58 +0900 Subject: [PATCH 02/14] Choose colours based on relative performance goals --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 222c30c1dc..a999c1338a 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -4,11 +4,13 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osuTK; @@ -21,13 +23,16 @@ namespace osu.Game.Graphics.UserInterface private Container mainContent = null!; + [Resolved] + private OsuColour colours { get; set; } = null!; + public FPSCounter() { AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChildren = new Drawable[] { @@ -41,7 +46,6 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Colour = colours.Orange2, }, fpsCounter = new FramesPerSecondCounter { @@ -49,7 +53,6 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.TopRight, Y = 11, Scale = new Vector2(0.8f), - Colour = colours.Lime3, } } }, @@ -97,8 +100,27 @@ namespace osu.Game.Graphics.UserInterface if (hasSignificantChanges) displayTemporarily(); - msCounter.Current.Value = newFrameTime; + // If the frame time spikes up, make sure it shows immediately on the counter. + if (msCounter.Current.Value < 20 && newFrameTime > 20) + msCounter.SetCountWithoutRolling(newFrameTime); + else + msCounter.Current.Value = newFrameTime; + fpsCounter.Current.Value = newFps; + + fpsCounter.Colour = getColour(fpsCounter.DisplayedCount / gameHost.DrawThread.Clock.MaximumUpdateHz); + + double equivalentHz = 1000 / msCounter.DisplayedCount; + + msCounter.Colour = getColour(equivalentHz / gameHost.UpdateThread.Clock.MaximumUpdateHz); + } + + private ColourInfo getColour(double performanceRatio) + { + if (performanceRatio < 0.5f) + return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5, Easing.Out); + + return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime3, 0.5, 1, Easing.Out); } public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); From 0fb959a565023b4939588b4d97e713bbd6da6606 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:29:18 +0900 Subject: [PATCH 03/14] Stay displayed while hovering --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index a999c1338a..58be2ff5a2 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; @@ -23,6 +25,10 @@ namespace osu.Game.Graphics.UserInterface private Container mainContent = null!; + private Container background = null!; + + private const float idle_background_alpha = 0.4f; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -39,9 +45,24 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - Size = new Vector2(30), + AutoSizeAxes = Axes.Both, Children = new Drawable[] { + background = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + Alpha = idle_background_alpha, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray0, + RelativeSizeAxes = Axes.Both, + }, + } + }, msCounter = new FrameTimeCounter { Anchor = Anchor.TopRight, @@ -65,6 +86,20 @@ namespace osu.Game.Graphics.UserInterface displayTemporarily(); } + protected override bool OnHover(HoverEvent e) + { + background.FadeTo(1, 200); + displayTemporarily(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeTo(idle_background_alpha, 200); + displayTemporarily(); + base.OnHoverLost(e); + } + private bool isDisplayed; private ScheduledDelegate? fadeOutDelegate; @@ -75,11 +110,15 @@ namespace osu.Game.Graphics.UserInterface mainContent.FadeTo(1, 300, Easing.OutQuint); fadeOutDelegate?.Cancel(); - fadeOutDelegate = Scheduler.AddDelayed(() => + + if (!IsHovered) { - mainContent.FadeTo(0, 1000, Easing.In); - isDisplayed = false; - }, 2000); + fadeOutDelegate = Scheduler.AddDelayed(() => + { + mainContent.FadeTo(0, 1000, Easing.In); + isDisplayed = false; + }, 2000); + } } [Resolved] From 0a1744facaffd299e72d2c8ec8fe30ce29412ae1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:49:57 +0900 Subject: [PATCH 04/14] Add to game and bind with configuration setting --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 15 ++++++++++++++- osu.Game/OsuGame.cs | 7 +++++++ osu.Game/OsuGameBase.cs | 16 ---------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 58be2ff5a2..2dd945d375 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -13,6 +14,7 @@ using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osuTK; @@ -29,6 +31,8 @@ namespace osu.Game.Graphics.UserInterface private const float idle_background_alpha = 0.4f; + private Bindable showFpsDisplay = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -38,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { InternalChildren = new Drawable[] { @@ -78,12 +82,21 @@ namespace osu.Game.Graphics.UserInterface } }, }; + + showFpsDisplay = config.GetBindable(OsuSetting.ShowFpsDisplay); } protected override void LoadComplete() { base.LoadComplete(); + displayTemporarily(); + + showFpsDisplay.BindValueChanged(showFps => + { + this.FadeTo(showFps.NewValue ? 1 : 0, 100); + displayTemporarily(); + }, true); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bd0a2680ae..23af401dbd 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -814,6 +814,13 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; + loadComponentSingleFile(new FPSCounter + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(10), + }, topMostOverlayContent.Add); + if (!args?.Any(a => a == @"--no-version-overlay") ?? true) loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4b5c9c0815..d07d9379f4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -17,7 +17,6 @@ using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Handlers; @@ -192,8 +191,6 @@ namespace osu.Game private DependencyContainer dependencies; - private Bindable fpsDisplayVisible; - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); /// @@ -404,19 +401,6 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Black"); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // TODO: This is temporary until we reimplement the local FPS display. - // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. - fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; - fpsDisplayVisible.TriggerChange(); - - FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; - } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); From f54aff2ecef798c940a5579ad451ce0ed1b31f8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 21:05:20 +0900 Subject: [PATCH 05/14] Add global key binding for FPS toggle --- osu.Game/Configuration/OsuConfigManager.cs | 6 ++++++ osu.Game/Graphics/UserInterface/FPSCounter.cs | 17 ++++++++++++----- .../Input/Bindings/GlobalActionContainer.cs | 4 ++++ .../GlobalActionKeyBindingStrings.cs | 5 +++++ osu.Game/OsuGame.cs | 8 +++++++- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a523507205..e816fd50f3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -224,6 +224,12 @@ namespace osu.Game.Configuration return new TrackedSettings { + new TrackedSetting(OsuSetting.ShowFpsDisplay, state => new SettingDescription( + rawValue: state, + name: GlobalActionKeyBindingStrings.ToggleFPSCounter, + value: state ? CommonStrings.Enabled.ToLower() : CommonStrings.Disabled.ToLower(), + shortcut: LookupKeyBindings(GlobalAction.ToggleFPSDisplay)) + ), new TrackedSetting(OsuSetting.MouseDisableButtons, disabledState => new SettingDescription( rawValue: !disabledState, name: GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons, diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 2dd945d375..1d72f772db 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -20,7 +20,7 @@ using osuTK; namespace osu.Game.Graphics.UserInterface { - public class FPSCounter : CompositeDrawable, IHasCustomTooltip + public class FPSCounter : VisibilityContainer, IHasCustomTooltip { private RollingCounter msCounter = null!; private RollingCounter fpsCounter = null!; @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface private const float idle_background_alpha = 0.4f; - private Bindable showFpsDisplay = null!; + private readonly BindableBool showFpsDisplay = new BindableBool(true); [Resolved] private OsuColour colours { get; set; } = null!; @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.UserInterface }, }; - showFpsDisplay = config.GetBindable(OsuSetting.ShowFpsDisplay); + config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay); } protected override void LoadComplete() @@ -94,11 +94,18 @@ namespace osu.Game.Graphics.UserInterface showFpsDisplay.BindValueChanged(showFps => { - this.FadeTo(showFps.NewValue ? 1 : 0, 100); - displayTemporarily(); + State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden; + if (showFps.NewValue) + displayTemporarily(); }, true); + + State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible); } + protected override void PopIn() => this.FadeIn(100); + + protected override void PopOut() => this.FadeOut(100); + protected override bool OnHover(HoverEvent e) { background.FadeTo(1, 200); diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 21b49286ea..1ee03a6964 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -45,6 +45,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial), new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons), new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), @@ -328,5 +329,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))] EditorTapForBPM, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleFPSCounter))] + ToggleFPSDisplay, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 82d03dbb5b..de1a5b189c 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -274,6 +274,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ToggleSkinEditor => new TranslatableString(getKey(@"toggle_skin_editor"), @"Toggle skin editor"); + /// + /// "Toggle FPS counter" + /// + public static LocalisableString ToggleFPSCounter => new TranslatableString(getKey(@"toggle_fps_counter"), @"Toggle FPS counter"); + /// /// "Previous volume meter" /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 23af401dbd..c90b4c2403 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -159,6 +159,8 @@ namespace osu.Game protected FirstRunSetupOverlay FirstRunOverlay { get; private set; } + private FPSCounter fpsCounter; + private VolumeOverlay volume; private OsuLogo osuLogo; @@ -814,7 +816,7 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; - loadComponentSingleFile(new FPSCounter + loadComponentSingleFile(fpsCounter = new FPSCounter { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -1121,6 +1123,10 @@ namespace osu.Game switch (e.Action) { + case GlobalAction.ToggleFPSDisplay: + fpsCounter.ToggleVisibility(); + return true; + case GlobalAction.ToggleSkinEditor: skinEditor.ToggleVisibility(); return true; From 75453b78c0a39086c12ad04041503863c6d747e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 23:59:09 +0900 Subject: [PATCH 06/14] Adjust colours and metrics --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 14 +++++++++----- osu.Game/OsuGame.cs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 1d72f772db..001eedff39 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -49,13 +49,14 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - AutoSizeAxes = Axes.Both, + Size = new Vector2(42, 26), Children = new Drawable[] { background = new Container { RelativeSizeAxes = Axes.Both, CornerRadius = 5, + CornerExponent = 5f, Masking = true, Alpha = idle_background_alpha, Children = new Drawable[] @@ -71,12 +72,15 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Margin = new MarginPadding(1), + Y = -1, }, fpsCounter = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Y = 11, + Margin = new MarginPadding(1), + Y = 10, Scale = new Vector2(0.8f), } } @@ -135,7 +139,7 @@ namespace osu.Game.Graphics.UserInterface { fadeOutDelegate = Scheduler.AddDelayed(() => { - mainContent.FadeTo(0, 1000, Easing.In); + mainContent.FadeTo(0, 300, Easing.OutQuint); isDisplayed = false; }, 2000); } @@ -177,9 +181,9 @@ namespace osu.Game.Graphics.UserInterface private ColourInfo getColour(double performanceRatio) { if (performanceRatio < 0.5f) - return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5, Easing.Out); + return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5); - return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime3, 0.5, 1, Easing.Out); + return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime0, 0.5, 0.9); } public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c90b4c2403..69d02b95ff 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -820,7 +820,7 @@ namespace osu.Game { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding(10), + Margin = new MarginPadding(5), }, topMostOverlayContent.Add); if (!args?.Any(a => a == @"--no-version-overlay") ?? true) From c7313b4198179801e1ebdec47da70063cf5d052e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:58:28 +0900 Subject: [PATCH 07/14] Fix alignment glitching due to non-matching anchor/origin Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 001eedff39..aaaf6b5e9b 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -198,6 +198,8 @@ namespace osu.Game.Graphics.UserInterface { return new OsuSpriteText { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), Spacing = new Vector2(-2), }; @@ -217,6 +219,8 @@ namespace osu.Game.Graphics.UserInterface { return new OsuSpriteText { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), Spacing = new Vector2(-1), }; From 57ecc3a6dfea9b8c504da70329558d973d830d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:59:26 +0900 Subject: [PATCH 08/14] Remove unnecessary negative spacing from thread names Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs index af95fbe556..5a26d34f8d 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -39,7 +39,6 @@ namespace osu.Game.Graphics.UserInterface new OsuTextFlowContainer(cp => { cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); - cp.Spacing = new Vector2(-1); }) { AutoSizeAxes = Axes.Both, From e1a577ea48642cdf459309ce71ca4b3414deef4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:59:51 +0900 Subject: [PATCH 09/14] Adjust spacing to make things feel more even Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index aaaf6b5e9b..a73c3afaf4 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -73,13 +73,13 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), - Y = -1, + Y = -2, }, fpsCounter = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding(1), + Margin = new MarginPadding(2), Y = 10, Scale = new Vector2(0.8f), } From 728e22fbcecf36b76eb085fe822e8210ada63a22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:06:22 +0900 Subject: [PATCH 10/14] Improve tooltip display when running single thread --- osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs index 5a26d34f8d..bf53bff9b4 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -76,9 +76,11 @@ namespace osu.Game.Graphics.UserInterface { var clock = thread.Clock; - string maximum = $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}"; + string maximum = clock.Throttling + ? $"/{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}" + : string.Empty; - textFlow.AddParagraph($"{clock.FramesPerSecond:0}/{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); + textFlow.AddParagraph($"{clock.FramesPerSecond:0}{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); } } } From 285516b11185c4b62874181e1141996d713b5626 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:40:27 +0900 Subject: [PATCH 11/14] Fix `isDisplayed` never actually being set --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index a73c3afaf4..e0a4decf7f 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -131,9 +131,13 @@ namespace osu.Game.Graphics.UserInterface private void displayTemporarily() { if (!isDisplayed) + { mainContent.FadeTo(1, 300, Easing.OutQuint); + isDisplayed = true; + } fadeOutDelegate?.Cancel(); + fadeOutDelegate = null; if (!IsHovered) { From 705ff06ea5fcb09d175b21c938608f3c80937cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:50:39 +0900 Subject: [PATCH 12/14] Better handle spikes and significant changes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index e0a4decf7f..524a055903 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,8 +21,8 @@ namespace osu.Game.Graphics.UserInterface { public class FPSCounter : VisibilityContainer, IHasCustomTooltip { - private RollingCounter msCounter = null!; - private RollingCounter fpsCounter = null!; + private RollingCounter counterUpdateFrameTime = null!; + private RollingCounter counterDrawFPS = null!; private Container mainContent = null!; @@ -68,14 +67,14 @@ namespace osu.Game.Graphics.UserInterface }, } }, - msCounter = new FrameTimeCounter + counterUpdateFrameTime = new FrameTimeCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), Y = -2, }, - fpsCounter = new FramesPerSecondCounter + counterDrawFPS = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -156,30 +155,47 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - // TODO: this is wrong (elapsed clock time, not actual run time). - double newFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - double newFps = gameHost.DrawThread.Clock.FramesPerSecond; + double aimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; + double aimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; - bool hasSignificantChanges = - Math.Abs(msCounter.Current.Value - newFrameTime) > 5 || - Math.Abs(fpsCounter.Current.Value - newFps) > 10; + if (!gameHost.UpdateThread.Clock.Throttling) + { + aimUpdateFPS = aimDrawFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + } + + // TODO: this is wrong (elapsed clock time, not actual run time). + double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; + // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. + double newDrawFps = 1000 / gameHost.DrawThread.Clock.ElapsedFrameTime; + + const double spike_time_ms = 20; + + bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFps <= (1000 / spike_time_ms); + + // If the frame time spikes up, make sure it shows immediately on the counter. + if (hasUpdateSpike) + counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime); + else + counterUpdateFrameTime.Current.Value = newUpdateFrameTime; + + if (hasDrawSpike) + counterDrawFPS.SetCountWithoutRolling(newDrawFps); + else + counterDrawFPS.Current.Value = newDrawFps; + + counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS); + + double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; + counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); + + bool hasSignificantChanges = hasDrawSpike + || hasUpdateSpike + || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 + || displayedUpdateFPS < aimUpdateFPS * 0.8; if (hasSignificantChanges) displayTemporarily(); - - // If the frame time spikes up, make sure it shows immediately on the counter. - if (msCounter.Current.Value < 20 && newFrameTime > 20) - msCounter.SetCountWithoutRolling(newFrameTime); - else - msCounter.Current.Value = newFrameTime; - - fpsCounter.Current.Value = newFps; - - fpsCounter.Colour = getColour(fpsCounter.DisplayedCount / gameHost.DrawThread.Clock.MaximumUpdateHz); - - double equivalentHz = 1000 / msCounter.DisplayedCount; - - msCounter.Colour = getColour(equivalentHz / gameHost.UpdateThread.Clock.MaximumUpdateHz); } private ColourInfo getColour(double performanceRatio) @@ -196,7 +212,7 @@ namespace osu.Game.Graphics.UserInterface public class FramesPerSecondCounter : RollingCounter { - protected override double RollingDuration => 400; + protected override double RollingDuration => 1000; protected override OsuSpriteText CreateSpriteText() { From 311a0a3de019657adb600798e72261ea6db76cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:57:38 +0900 Subject: [PATCH 13/14] Always show counter temporarily when aim FPS changes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 524a055903..40aa223ddc 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -127,6 +127,9 @@ namespace osu.Game.Graphics.UserInterface private ScheduledDelegate? fadeOutDelegate; + private double aimDrawFPS; + private double aimUpdateFPS; + private void displayTemporarily() { if (!isDisplayed) @@ -155,13 +158,9 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - double aimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; - double aimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; - - if (!gameHost.UpdateThread.Clock.Throttling) - { - aimUpdateFPS = aimDrawFPS = gameHost.InputThread.Clock.MaximumUpdateHz; - } + // Handle the case where the window has become inactive or the user changed the + // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). + bool aimRatesChanged = updateAimFPS(); // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; @@ -189,7 +188,8 @@ namespace osu.Game.Graphics.UserInterface double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); - bool hasSignificantChanges = hasDrawSpike + bool hasSignificantChanges = aimRatesChanged + || hasDrawSpike || hasUpdateSpike || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 || displayedUpdateFPS < aimUpdateFPS * 0.8; @@ -198,6 +198,34 @@ namespace osu.Game.Graphics.UserInterface displayTemporarily(); } + private bool updateAimFPS() + { + if (gameHost.UpdateThread.Clock.Throttling) + { + double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; + double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; + + if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS) + { + aimDrawFPS = newAimDrawFPS; + aimUpdateFPS = newAimUpdateFPS; + return true; + } + } + else + { + double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + + if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS) + { + aimUpdateFPS = aimDrawFPS = newAimFPS; + return true; + } + } + + return false; + } + private ColourInfo getColour(double performanceRatio) { if (performanceRatio < 0.5f) From 56106e43d2244b03472ade2e6e375599c0ed0c5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 13:06:43 +0900 Subject: [PATCH 14/14] Avoid div-by-zero --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 40aa223ddc..9d5aa525d7 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -165,7 +166,7 @@ namespace osu.Game.Graphics.UserInterface // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - double newDrawFps = 1000 / gameHost.DrawThread.Clock.ElapsedFrameTime; + double newDrawFps = 1000 / Math.Max(0.001, gameHost.DrawThread.Clock.ElapsedFrameTime); const double spike_time_ms = 20;