Skip to content

Commit 51bb980

Browse files
authored
Merge pull request #263 from winnerspiros/copilot/fix-vulkan-related-issues
Android Performance settings: trim crash-related toggles, expand Oboe hint
2 parents 3957346 + a883716 commit 51bb980

9 files changed

Lines changed: 137 additions & 81 deletions

File tree

osu.Android/AndroidNativeBridgeManager.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Runtime.CompilerServices;
66
using osu.Android.Native;
7+
using osu.Framework.Logging;
78
using osu.Framework.Threading;
89
using Debug = System.Diagnostics.Debug;
910

@@ -32,7 +33,7 @@ public void StartOboeBridge(Scheduler scheduler, Action<double> onLatencyMeasure
3233
return;
3334
}
3435

35-
Debug.WriteLine($"[osu!] Starting Oboe bridge (sampleRate={sampleRate}, hasProvider={provider != IntPtr.Zero})");
36+
Logger.Log($"[osu!] Starting Oboe bridge (sampleRate={sampleRate}, hasProvider={provider != IntPtr.Zero})");
3637
cachedOboeStatus = null;
3738

3839
try
@@ -69,7 +70,7 @@ public void StartOboeBridge(Scheduler scheduler, Action<double> onLatencyMeasure
6970

7071
if (started)
7172
{
72-
Debug.WriteLine("[osu!] Oboe bridge started successfully");
73+
Logger.Log("[osu!] Oboe bridge started successfully");
7374
logOboeInfo(bridge);
7475

7576
onStarted?.Invoke(bridge.SampleRate);
@@ -87,17 +88,17 @@ public void StartOboeBridge(Scheduler scheduler, Action<double> onLatencyMeasure
8788
else
8889
{
8990
string error = bridge.GetLastErrorMessage() ?? "Unknown";
90-
Debug.WriteLine($"[osu!] Oboe bridge created but failed to start: {error}");
91+
Logger.Log($"[osu!] Oboe bridge created but failed to start: {error}", level: LogLevel.Error);
9192
}
9293
}
9394
else
9495
{
95-
Debug.WriteLine("[osu!] Oboe bridge creation failed — native library not loaded or stream open failed");
96+
Logger.Log("[osu!] Oboe bridge creation failed — native library not loaded or stream open failed", level: LogLevel.Error);
9697
}
9798
}
9899
catch (Exception e)
99100
{
100-
Debug.WriteLine($"[osu!] Oboe bridge init failed with exception: {e.Message}");
101+
Logger.Log($"[osu!] Oboe bridge init failed with exception: {e.Message}", level: LogLevel.Error);
101102
}
102103
}
103104
}
@@ -107,11 +108,11 @@ public void StopOboeBridge()
107108
{
108109
lock (oboeLock)
109110
{
110-
Debug.WriteLine("[osu!] Stopping Oboe bridge...");
111+
Logger.Log("[osu!] Stopping Oboe bridge...");
111112
(oboeBridge as OboeAudioBridge)?.Dispose();
112113
oboeBridge = null;
113114
cachedOboeStatus = null;
114-
Debug.WriteLine("[osu!] Oboe bridge stopped");
115+
Logger.Log("[osu!] Oboe bridge stopped");
115116
}
116117
}
117118

osu.Android/AndroidStartupFlags.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ internal static class AndroidStartupFlags
4242
public const string FLAG_DEFER_NATIVE_INIT_DISABLED = "android_startup_disable_defer_native_init.flag";
4343
public const string FLAG_FRAME_SYNC_MIGRATION_ENABLED = "android_startup_enable_frame_sync_migration.flag";
4444

45+
/// <summary>
46+
/// Verbose-logging opt-in sentinel. Presence ⇒ "user has enabled
47+
/// verbose framework logging". Absence ⇒ "default, quiet logging
48+
/// (Important+ only)". Quiet is the default because the framework's
49+
/// runtime/input log is ~330+ KB per launch on Android (mostly
50+
/// OpenTabletDriver detection + SDL platform chatter) and is not
51+
/// useful in the steady state. Toggle from
52+
/// Settings → Graphics → Android Performance.
53+
/// </summary>
54+
public const string FLAG_VERBOSE_LOGGING_ENABLED = "android_startup_enable_verbose_logging.flag";
55+
4556
/// <summary>
4657
/// "Startup in progress" sentinel. Dropped near the very top of <see cref="OsuGameActivity.OnCreate"/>
4758
/// and cleared a few seconds after <c>OsuGame.LoadComplete</c> by <see cref="OsuGameAndroid"/>.

osu.Android/LogManagement.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,31 @@ internal static class LogManagement
5858
/// </summary>
5959
public static void Apply()
6060
{
61-
// NOTE: we used to force Logger.Level = LogLevel.Important here to
62-
// shrink runtime log output during the "log explosion" debugging
63-
// window. That has been reverted at user request — the default
64-
// framework log verbosity is now restored so osu.log captures the
65-
// full per-thread startup narrative we need to diagnose hangs.
66-
// Log size is still bounded by pruneLogDirectory() below
67-
// (MAX_LOG_BYTES cap with oldest-first eviction), so re-enabling
68-
// verbose logging cannot regress the on-disk footprint that the
69-
// 480 MB report originally exposed.
61+
// Verbose-logging toggle (default OFF). The framework writes
62+
// ~330 KB of runtime.log + ~28 KB of input.log per launch at the
63+
// default Verbose level, dominated by OpenTabletDriver per-tablet
64+
// detection and SDL platform-feature probe chatter — useful when
65+
// diagnosing a hang, not useful in the steady state. Default to
66+
// Important so on-disk log volume drops to a few KB per launch
67+
// and audio/render hot paths spend zero time formatting log
68+
// messages. Users can re-enable verbose logging from
69+
// Settings → Graphics → Android Performance to capture a full
70+
// log when they need to share one.
71+
//
72+
// Sentinel-driven (not config-driven) because LogManagement.Apply
73+
// runs in OsuGameActivity.OnCreate, LONG before the
74+
// OsuConfigManager exists — same pattern as the other Android
75+
// startup-safety flags. OsuGameAndroid mirrors the in-game
76+
// bindable into the sentinel via mirrorStartupFlag.
77+
try
78+
{
79+
bool verbose = AndroidStartupFlags.IsSet(AndroidStartupFlags.FLAG_VERBOSE_LOGGING_ENABLED);
80+
Logger.Level = verbose ? LogLevel.Verbose : LogLevel.Important;
81+
}
82+
catch (Exception e)
83+
{
84+
Debug.WriteLine($"[osu!] LogManagement: could not apply Logger.Level: {e.Message}");
85+
}
7086

7187
try
7288
{

osu.Android/OsuGameAndroid.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public partial class OsuGameAndroid : OsuGame
8585
private readonly Bindable<bool> cleanupStaleRealmFifos = new Bindable<bool>();
8686
private readonly Bindable<bool> deferStartupNativeInit = new Bindable<bool>();
8787
private readonly Bindable<bool> startupFrameSyncMigrationEnabled = new Bindable<bool>();
88+
private readonly Bindable<bool> verboseLogging = new Bindable<bool>();
8889

8990
[Cached(typeof(IHighPerformanceSessionManager))]
9091
private readonly IHighPerformanceSessionManager highPerformanceSessionManager = new AndroidHighPerformanceSessionManager();
@@ -208,16 +209,19 @@ private void load(FrameworkConfigManager frameworkConfig)
208209
LocalConfig.BindWith(OsuSetting.AndroidCleanupStaleRealmFifos, cleanupStaleRealmFifos);
209210
LocalConfig.BindWith(OsuSetting.AndroidDeferStartupNativeInit, deferStartupNativeInit);
210211
LocalConfig.BindWith(OsuSetting.AndroidStartupFrameSyncMigrationEnabled, startupFrameSyncMigrationEnabled);
212+
LocalConfig.BindWith(OsuSetting.AndroidVerboseLogging, verboseLogging);
211213

212214
// sentinelOnDisable=true → presence ⇒ "feature disabled". The
213215
// safety nets default to ON, so the sentinel is created only
214216
// when the user explicitly disables them.
215217
mirrorStartupFlag(cleanupStaleRealmFifos, AndroidStartupFlags.FLAG_CLEANUP_REALM_FIFOS_DISABLED, sentinelOnDisable: true);
216218
mirrorStartupFlag(deferStartupNativeInit, AndroidStartupFlags.FLAG_DEFER_NATIVE_INIT_DISABLED, sentinelOnDisable: true);
217219
// sentinelOnDisable=false → presence ⇒ "feature enabled". The
218-
// FrameSync migration defaults to OFF, so the sentinel is
219-
// created only when the user explicitly opts in.
220+
// FrameSync migration and verbose-logging toggles both default
221+
// to OFF, so the sentinel is created only when the user
222+
// explicitly opts in.
220223
mirrorStartupFlag(startupFrameSyncMigrationEnabled, AndroidStartupFlags.FLAG_FRAME_SYNC_MIGRATION_ENABLED, sentinelOnDisable: false);
224+
mirrorStartupFlag(verboseLogging, AndroidStartupFlags.FLAG_VERBOSE_LOGGING_ENABLED, sentinelOnDisable: false);
221225
}
222226
catch (Exception e)
223227
{
@@ -1211,17 +1215,21 @@ private void handleLowLatencyAudioChanged(ValueChangedEvent<bool> e)
12111215
{
12121216
double suggested = Math.Clamp(-latency, audioOffset.MinValue, audioOffset.MaxValue);
12131217
audioOffset.Value = suggested;
1214-
Debug.WriteLine($"[osu!] Audio offset auto-suggested: {suggested:F1}ms (hardware latency={latency:F1}ms)");
1218+
Logger.Log($"[osu!] Audio offset auto-suggested: {suggested:F1}ms (hardware latency={latency:F1}ms)");
12151219
}, audioRedirector != null ? audioRedirector.Provider : IntPtr.Zero, sampleRate =>
12161220
{
12171221
audioRedirector?.RefreshMixers(sampleRate);
1218-
Debug.WriteLine("[osu!] Audio redirector refreshed with hardware sample rate: " + sampleRate);
1222+
Logger.Log("[osu!] Audio redirector refreshed with hardware sample rate: " + sampleRate);
12191223
});
12201224
}
12211225
catch (Exception ex)
12221226
{
1223-
Debug.WriteLine($"[osu!] Failed to start Oboe bridge: {ex.Message}");
1224-
lowLatencyAudio.Value = false;
1227+
// Surface to runtime.log so a user-shared log makes Oboe failures
1228+
// diagnosable. Do NOT silently flip lowLatencyAudio.Value back to
1229+
// false here — persisting that flip turns a single transient init
1230+
// failure into a permanent "Oboe doesn't work" for the user, with
1231+
// no indication that the toggle was overridden behind their back.
1232+
Logger.Log($"[osu!] Failed to start Oboe bridge: {ex.Message}", level: LogLevel.Error);
12251233
}
12261234
}
12271235
else

osu.Android/Performance/AndroidHighPerformanceSessionManager.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ public class AndroidHighPerformanceSessionManager : IHighPerformanceSessionManag
1818

1919
private GCLatencyMode originalGCMode;
2020

21+
/// <summary>
22+
/// One-shot disable. Mono on Android throws <see cref="PlatformNotSupportedException"/>
23+
/// from the <see cref="GCSettings.LatencyMode"/> setter (and, on some runtimes, the
24+
/// getter). We must not let that exception escape — it would crash the
25+
/// game every time the user enters <c>PlayerLoader</c>, holds a mouse
26+
/// button, or otherwise triggers a high-performance session, since
27+
/// <see cref="BeginSession"/> is invoked on the update thread and the
28+
/// throw propagates up through <c>UpdateSubTree</c>.
29+
/// </summary>
30+
private static bool gcLatencyModeSupported = true;
31+
2132
public IDisposable BeginSession()
2233
{
2334
enterSession();
@@ -34,8 +45,23 @@ private void enterSession()
3445

3546
Logger.Log("Starting high performance session (Android)");
3647

37-
originalGCMode = GCSettings.LatencyMode;
38-
GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
48+
if (!gcLatencyModeSupported)
49+
return;
50+
51+
try
52+
{
53+
originalGCMode = GCSettings.LatencyMode;
54+
GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
55+
}
56+
catch (PlatformNotSupportedException)
57+
{
58+
// Mono on Android does not implement GCSettings.LatencyMode.
59+
// Latch off so subsequent sessions skip the throwing call entirely
60+
// (the unhandled-exception allowance is finite and would burn out
61+
// after a few gameplay entries, killing the process).
62+
gcLatencyModeSupported = false;
63+
Logger.Log("GCSettings.LatencyMode unsupported on this runtime; high-performance GC tuning disabled.");
64+
}
3965
}
4066

4167
private void exitSession()
@@ -48,8 +74,18 @@ private void exitSession()
4874

4975
Logger.Log("Ending high performance session (Android)");
5076

51-
if (GCSettings.LatencyMode == GCLatencyMode.SustainedLowLatency)
52-
GCSettings.LatencyMode = originalGCMode;
77+
if (!gcLatencyModeSupported)
78+
return;
79+
80+
try
81+
{
82+
if (GCSettings.LatencyMode == GCLatencyMode.SustainedLowLatency)
83+
GCSettings.LatencyMode = originalGCMode;
84+
}
85+
catch (PlatformNotSupportedException)
86+
{
87+
gcLatencyModeSupported = false;
88+
}
5389
}
5490
}
5591
}

osu.Game/Configuration/OsuConfigManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ protected override void InitialiseDefaults()
270270
SetDefault(OsuSetting.AndroidCleanupStaleRealmFifos, true);
271271
SetDefault(OsuSetting.AndroidDeferStartupNativeInit, true);
272272
SetDefault(OsuSetting.AndroidStartupFrameSyncMigrationEnabled, false);
273+
SetDefault(OsuSetting.AndroidVerboseLogging, false);
273274
}
274275

275276
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
@@ -532,6 +533,7 @@ public enum OsuSetting
532533
AndroidCleanupStaleRealmFifos,
533534
AndroidDeferStartupNativeInit,
534535
AndroidStartupFrameSyncMigrationEnabled,
536+
AndroidVerboseLogging,
535537
RefreshRateFullscreen,
536538
}
537539
}

osu.Game/Graphics/UserInterface/FPSCounter.cs

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,6 @@ public partial class FPSCounter : VisibilityContainer, IHasCustomTooltip
5959
[Resolved(canBeNull: true)]
6060
private OsuColour colours { get; set; } = null!;
6161

62-
[Resolved(canBeNull: true)]
63-
protected OsuGameBase? Game { get; private set; } = null!;
64-
65-
[Resolved(canBeNull: true)]
66-
private GameHost? host { get; set; } = null!;
67-
6862
public FPSCounter()
6963
{
7064
AutoSizeAxes = Axes.Both;
@@ -238,23 +232,7 @@ private void requestDisplay()
238232
private void updateFpsDisplay()
239233
{
240234
counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS);
241-
string status = $"{displayedFpsCount:#,0} fps";
242-
243-
if (Game != null)
244-
{
245-
status += $" | {host?.ResolvedRenderer.ToString()}";
246-
247-
if (Game.DisplayRefreshRate > 0)
248-
status += $" | {Game.DisplayRefreshRate}Hz";
249-
250-
if (!string.IsNullOrEmpty(Game.VulkanStatus))
251-
status += $" | {Game.VulkanStatus}";
252-
253-
if (Game.IsOboeEnabled)
254-
status += $" | Oboe: {Game.OboeStatus}{(Game.IsOboeActive ? $" ({Game.OboeLatency:F1}ms)" : "")}";
255-
}
256-
257-
counterDrawFPS.Text = status;
235+
counterDrawFPS.Text = $"{displayedFpsCount:#,0} fps";
258236
}
259237

260238
private void updateFrameTimeDisplay()

0 commit comments

Comments
 (0)