Skip to content

Commit e14baf6

Browse files
Max FPS: thread priority, sustained perf always-on, hardwareAccelerated, cutout mode
- Set THREAD_PRIORITY_URGENT_DISPLAY (-8) on update/draw/input threads - Always enable SustainedPerformanceMode (not gated behind toggle) - Add android:hardwareAccelerated=true to AndroidManifest - LayoutInDisplayCutoutMode.ShortEdges for full render area - Recycle dummy MotionEvent from early unbuffered dispatch - Fix integer overflow in affinity mask for 32+ core devices - Vulkan only shown when IsVulkanRecommended (not just supported) - Lazy VulkanProbe: no startup cost, only when setting enabled Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/62a8048b-6fe1-424d-9c00-a3fbddf6ba74 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
1 parent 2c94767 commit e14baf6

4 files changed

Lines changed: 35 additions & 12 deletions

File tree

osu.Android/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version='1.0' encoding='utf-8'?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
33
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="36" />
4-
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher" android:largeHeap="true">
4+
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher" android:largeHeap="true" android:hardwareAccelerated="true">
55
<provider android:name="androidx.core.content.FileProvider" android:authorities="sh.ppy.osulazer.fileprovider" android:grantUriPermissions="true" android:exported="false">
66
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
77
</provider>

osu.Android/AndroidNativeBridgeManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void StartOboeBridge(Scheduler scheduler, Action<double> onLatencyMeasure
5555
for (int i = bigStart; i < Math.Min(cores, 32); i++)
5656
audioAffinityMask |= 1 << i;
5757

58-
if (audioAffinityMask == 0) audioAffinityMask = (1 << cores) - 1;
58+
if (audioAffinityMask == 0) audioAffinityMask = (1 << Math.Min(cores, 31)) - 1;
5959

6060
try { SetThreadAffinity(audioAffinityMask); }
6161
catch (Exception e) { Debug.WriteLine($"[osu!] Audio thread affinity failed: {e.Message}"); }

osu.Android/OsuGameActivity.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,19 @@ protected override void OnCreate(Bundle? savedInstanceState)
9393
Window.AddFlags(WindowManagerFlags.Fullscreen);
9494
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
9595

96+
// Use full display area including camera cutout/notch for maximum render space.
97+
if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window.Attributes != null)
98+
Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
99+
96100
// Request unbuffered touch dispatch early for minimum input latency.
97-
// This applies to all subsequent touch events for this window.
98101
if (OperatingSystem.IsAndroidVersionAtLeast(21))
99102
{
100-
try { Window.DecorView?.RequestUnbufferedDispatch(MotionEvent.Obtain(0, 0, MotionEventActions.Down, 0, 0, 0)); }
103+
try
104+
{
105+
var dummy = MotionEvent.Obtain(0, 0, MotionEventActions.Down, 0, 0, 0);
106+
Window.DecorView?.RequestUnbufferedDispatch(dummy);
107+
dummy?.Recycle();
108+
}
101109
catch { /* best-effort; will also be requested per-event in dispatch methods */ }
102110
}
103111

osu.Android/OsuGameAndroid.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -207,25 +207,38 @@ protected override void LoadComplete()
207207
affinityMask |= 1 << i;
208208

209209
if (affinityMask == 0)
210-
affinityMask = (1 << coreCount) - 1; // fallback: use all cores
210+
affinityMask = (1 << Math.Min(coreCount, 31)) - 1;
211211

212212
try
213213
{
214214
if (OboeAudioBridge.nSetThreadAffinity(affinityMask) != 0)
215215
Logger.Log($"[osu!] Update thread pinned to big cores (mask=0x{affinityMask:X}, cores {bigCoreStart}-{coreCount - 1})", LoggingTarget.Performance);
216216

217+
// Set update thread to urgent display priority (-8) for minimum scheduling latency.
218+
global::Android.OS.Process.SetThreadPriority(global::Android.OS.ThreadPriority.UrgentDisplay);
219+
217220
int mask = affinityMask;
218221

219222
Scheduler.Add(() =>
220223
{
221224
Host.DrawThread.Scheduler.Add(() =>
222225
{
223-
try { if (OboeAudioBridge.nSetThreadAffinity(mask) != 0) Logger.Log("[osu!] Render thread pinned to big cores", LoggingTarget.Performance); } catch { }
226+
try
227+
{
228+
if (OboeAudioBridge.nSetThreadAffinity(mask) != 0) Logger.Log("[osu!] Render thread pinned to big cores", LoggingTarget.Performance);
229+
global::Android.OS.Process.SetThreadPriority(global::Android.OS.ThreadPriority.UrgentDisplay);
230+
}
231+
catch { }
224232
});
225233

226234
Host.InputThread.Scheduler.Add(() =>
227235
{
228-
try { if (OboeAudioBridge.nSetThreadAffinity(mask) != 0) Logger.Log("[osu!] Input thread pinned to big cores", LoggingTarget.Performance); } catch { }
236+
try
237+
{
238+
if (OboeAudioBridge.nSetThreadAffinity(mask) != 0) Logger.Log("[osu!] Input thread pinned to big cores", LoggingTarget.Performance);
239+
global::Android.OS.Process.SetThreadPriority(global::Android.OS.ThreadPriority.UrgentDisplay);
240+
}
241+
catch { }
229242
});
230243
});
231244
}
@@ -234,6 +247,11 @@ protected override void LoadComplete()
234247
Logger.Log($"[osu!] Failed to pin threads: {e.Message}", LoggingTarget.Performance);
235248
}
236249

250+
// Always enable sustained performance mode for consistent frame delivery.
251+
// This prevents thermal throttling from causing sudden FPS drops.
252+
try { gameActivity.Window?.SetSustainedPerformanceMode(true); }
253+
catch { }
254+
237255
base.LoadComplete();
238256

239257
// Always select the highest refresh rate on startup, regardless of performance mode.
@@ -362,8 +380,8 @@ private void applyPerformanceOptimizations(bool enabled)
362380
{
363381
try
364382
{
365-
gameActivity.Window?.SetSustainedPerformanceMode(enabled);
366-
383+
// Sustained performance mode is always on (set in LoadComplete).
384+
// The performance toggle controls the high-perf GC session only.
367385
if (enabled)
368386
{
369387
highPerformanceSession ??= highPerformanceSessionManager.BeginSession();
@@ -373,9 +391,6 @@ private void applyPerformanceOptimizations(bool enabled)
373391
highPerformanceSession?.Dispose();
374392
highPerformanceSession = null;
375393
}
376-
377-
// Refresh rate is controlled by SelectHighestRefreshRate() (called once in LoadComplete)
378-
// and the user's SelectedDisplayRefreshRate bindable. No need to re-apply here.
379394
}
380395
catch (Exception e)
381396
{

0 commit comments

Comments
 (0)