Skip to content

Commit b776793

Browse files
CopilotCopilot
andcommitted
Add Android performance optimizations for low-latency gameplay
- AndroidManifest.xml: Add largeHeap=true to prevent OOM crashes - OsuGameActivity: Add unbuffered touch dispatch, sustained performance mode, highest refresh rate selection, GameManager integration, null-safe OnCreate - OsuGameAndroid: Add performance mode bindable tied to OsuSetting - OsuConfigManager: Add AndroidPerformanceMode setting (default: true) - Native/: Add Oboe low-latency audio bridge and Vulkan capability probe with safe C# P/Invoke wrappers (all calls wrapped in try/catch) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2b587c8 commit b776793

11 files changed

Lines changed: 866 additions & 12 deletions

osu.Android/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
android:supportsRtl="true"
66
android:label="osu!"
77
android:icon="@mipmap/ic_launcher"
8-
android:roundIcon="@mipmap/ic_launcher">
8+
android:roundIcon="@mipmap/ic_launcher"
9+
android:largeHeap="true">
910
<provider android:name="androidx.core.content.FileProvider"
1011
android:authorities="sh.ppy.osulazer.fileprovider"
1112
android:grantUriPermissions="true"

osu.Android/Native/CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
cmake_minimum_required(VERSION 3.18)
2+
project(osu_native LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
6+
find_package(oboe REQUIRED CONFIG)
7+
find_library(vulkan-lib vulkan)
8+
find_library(log-lib log)
9+
find_library(android-lib android)
10+
11+
add_library(osu_native SHARED
12+
oboe_bridge.cpp
13+
vulkan_bridge.cpp
14+
)
15+
16+
target_link_libraries(osu_native
17+
oboe::oboe
18+
${vulkan-lib}
19+
${log-lib}
20+
${android-lib}
21+
)
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using Debug = System.Diagnostics.Debug;
7+
8+
namespace osu.Android.Native
9+
{
10+
/// <summary>
11+
/// Managed wrapper around the native Oboe low-latency audio bridge.
12+
/// Provides accurate audio output latency measurement for rhythm-game synchronisation.
13+
/// </summary>
14+
public sealed class OboeAudioBridge : IDisposable
15+
{
16+
private long nativePtr;
17+
private bool disposed;
18+
19+
/// <summary>
20+
/// Creates and opens a new low-latency Oboe audio stream.
21+
/// Returns null if native library or stream creation fails.
22+
/// </summary>
23+
public static OboeAudioBridge? Create()
24+
{
25+
try
26+
{
27+
long ptr = nOboeCreate();
28+
29+
if (ptr == 0)
30+
{
31+
Debug.WriteLine("[osu!] Oboe stream creation failed (native returned null)");
32+
return null;
33+
}
34+
35+
return new OboeAudioBridge(ptr);
36+
}
37+
catch (DllNotFoundException)
38+
{
39+
Debug.WriteLine("[osu!] Native library not found, Oboe unavailable");
40+
return null;
41+
}
42+
catch (Exception e)
43+
{
44+
Debug.WriteLine($"[osu!] Oboe creation failed: {e.Message}");
45+
return null;
46+
}
47+
}
48+
49+
private OboeAudioBridge(long ptr)
50+
{
51+
nativePtr = ptr;
52+
}
53+
54+
/// <summary>
55+
/// Starts the audio output stream.
56+
/// </summary>
57+
/// <returns>True if the stream started successfully.</returns>
58+
public bool Start()
59+
{
60+
if (disposed || nativePtr == 0) return false;
61+
62+
try
63+
{
64+
return nOboeStart(nativePtr) != 0;
65+
}
66+
catch (Exception e)
67+
{
68+
Debug.WriteLine($"[osu!] Oboe start failed: {e.Message}");
69+
return false;
70+
}
71+
}
72+
73+
/// <summary>
74+
/// Stops the audio output stream.
75+
/// </summary>
76+
public void Stop()
77+
{
78+
if (disposed || nativePtr == 0) return;
79+
80+
try
81+
{
82+
nOboeStop(nativePtr);
83+
}
84+
catch (Exception e)
85+
{
86+
Debug.WriteLine($"[osu!] Oboe stop failed: {e.Message}");
87+
}
88+
}
89+
90+
/// <summary>
91+
/// Returns the measured audio output latency in milliseconds, or -1 if unavailable.
92+
/// This can be used to automatically calibrate audio offset for gameplay.
93+
/// </summary>
94+
public double GetOutputLatencyMs()
95+
{
96+
if (disposed || nativePtr == 0) return -1;
97+
98+
try
99+
{
100+
return nOboeGetLatencyMs(nativePtr);
101+
}
102+
catch
103+
{
104+
return -1;
105+
}
106+
}
107+
108+
/// <summary>
109+
/// Returns true if the Oboe stream is currently running.
110+
/// </summary>
111+
public bool IsActive
112+
{
113+
get
114+
{
115+
if (disposed || nativePtr == 0) return false;
116+
117+
try
118+
{
119+
return nOboeIsActive(nativePtr) != 0;
120+
}
121+
catch
122+
{
123+
return false;
124+
}
125+
}
126+
}
127+
128+
public void Dispose()
129+
{
130+
if (disposed) return;
131+
132+
disposed = true;
133+
134+
if (nativePtr != 0)
135+
{
136+
try
137+
{
138+
nOboeDestroy(nativePtr);
139+
}
140+
catch (Exception e)
141+
{
142+
Debug.WriteLine($"[osu!] Oboe dispose failed: {e.Message}");
143+
}
144+
145+
nativePtr = 0;
146+
}
147+
148+
GC.SuppressFinalize(this);
149+
}
150+
151+
~OboeAudioBridge()
152+
{
153+
Dispose();
154+
}
155+
156+
[DllImport("osu_native")]
157+
private static extern long nOboeCreate();
158+
159+
[DllImport("osu_native")]
160+
private static extern void nOboeDestroy(long ptr);
161+
162+
[DllImport("osu_native")]
163+
private static extern byte nOboeStart(long ptr);
164+
165+
[DllImport("osu_native")]
166+
private static extern void nOboeStop(long ptr);
167+
168+
[DllImport("osu_native")]
169+
private static extern double nOboeGetLatencyMs(long ptr);
170+
171+
[DllImport("osu_native")]
172+
private static extern byte nOboeIsActive(long ptr);
173+
}
174+
}

osu.Android/Native/VulkanProbe.cs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using Debug = System.Diagnostics.Debug;
7+
8+
namespace osu.Android.Native
9+
{
10+
/// <summary>
11+
/// Probes Vulkan GPU capability on the current Android device.
12+
/// Used to report hardware information and determine optimal rendering strategy.
13+
/// </summary>
14+
public sealed class VulkanProbe : IDisposable
15+
{
16+
private long nativePtr;
17+
private bool disposed;
18+
19+
/// <summary>
20+
/// Creates a Vulkan probe. Returns null if native library is unavailable.
21+
/// </summary>
22+
public static VulkanProbe? Create()
23+
{
24+
try
25+
{
26+
long ptr = nVulkanProbeCreate();
27+
28+
if (ptr == 0)
29+
{
30+
Debug.WriteLine("[osu!] Vulkan probe creation failed");
31+
return null;
32+
}
33+
34+
return new VulkanProbe(ptr);
35+
}
36+
catch (DllNotFoundException)
37+
{
38+
Debug.WriteLine("[osu!] Native library not found, Vulkan probe unavailable");
39+
return null;
40+
}
41+
catch (Exception e)
42+
{
43+
Debug.WriteLine($"[osu!] Vulkan probe failed: {e.Message}");
44+
return null;
45+
}
46+
}
47+
48+
private VulkanProbe(long ptr)
49+
{
50+
nativePtr = ptr;
51+
}
52+
53+
/// <summary>
54+
/// Whether Vulkan is available on this device.
55+
/// </summary>
56+
public bool IsAvailable
57+
{
58+
get
59+
{
60+
if (disposed || nativePtr == 0) return false;
61+
62+
try
63+
{
64+
return nVulkanIsAvailable(nativePtr) != 0;
65+
}
66+
catch
67+
{
68+
return false;
69+
}
70+
}
71+
}
72+
73+
/// <summary>
74+
/// The Vulkan API version supported by the device, encoded as per Vulkan spec.
75+
/// </summary>
76+
public int ApiVersion
77+
{
78+
get
79+
{
80+
if (disposed || nativePtr == 0) return 0;
81+
82+
try
83+
{
84+
return nVulkanGetApiVersion(nativePtr);
85+
}
86+
catch
87+
{
88+
return 0;
89+
}
90+
}
91+
}
92+
93+
/// <summary>
94+
/// Whether the device supports VK_KHR_swapchain (required for rendering).
95+
/// </summary>
96+
public bool SupportsSwapchain
97+
{
98+
get
99+
{
100+
if (disposed || nativePtr == 0) return false;
101+
102+
try
103+
{
104+
return nVulkanSupportsSwapchain(nativePtr) != 0;
105+
}
106+
catch
107+
{
108+
return false;
109+
}
110+
}
111+
}
112+
113+
public void Dispose()
114+
{
115+
if (disposed) return;
116+
117+
disposed = true;
118+
119+
if (nativePtr != 0)
120+
{
121+
try
122+
{
123+
nVulkanProbeDestroy(nativePtr);
124+
}
125+
catch (Exception e)
126+
{
127+
Debug.WriteLine($"[osu!] Vulkan probe dispose failed: {e.Message}");
128+
}
129+
130+
nativePtr = 0;
131+
}
132+
133+
GC.SuppressFinalize(this);
134+
}
135+
136+
~VulkanProbe()
137+
{
138+
Dispose();
139+
}
140+
141+
[DllImport("osu_native")]
142+
private static extern long nVulkanProbeCreate();
143+
144+
[DllImport("osu_native")]
145+
private static extern void nVulkanProbeDestroy(long ptr);
146+
147+
[DllImport("osu_native")]
148+
private static extern byte nVulkanIsAvailable(long ptr);
149+
150+
[DllImport("osu_native")]
151+
private static extern int nVulkanGetApiVersion(long ptr);
152+
153+
[DllImport("osu_native")]
154+
private static extern byte nVulkanSupportsSwapchain(long ptr);
155+
}
156+
}

0 commit comments

Comments
 (0)