Skip to content

Commit 231e999

Browse files
authored
Merge pull request #135 from winnerspiros/fix/android-vulkan-oboe-perf-7832751681472622993
Android: Improve Vulkan stability and Oboe diagnostics
2 parents 8759a5d + 1a9921b commit 231e999

11 files changed

Lines changed: 879 additions & 80 deletions

osu.Android/AndroidNativeBridgeManager.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,15 @@ public void StartOboeBridge(Scheduler scheduler, Action<double> onLatencyMeasure
5454

5555
onStarted?.Invoke(bridge.SampleRate);
5656

57-
scheduler.AddDelayed(() =>
57+
scheduler.Add(new ScheduledDelegate(() =>
5858
{
5959
if (oboeBridge is not OboeAudioBridge b) return;
6060

6161
double latency = b.GetOutputLatencyMs();
62-
Debug.WriteLine($"[osu!] Oboe measured latency after warm-up: {latency:F1}ms");
6362

6463
if (latency > 0)
6564
onLatencyMeasured(latency);
66-
}, 2000);
65+
}, 2000, 5000));
6766
}
6867
else
6968
{
@@ -87,6 +86,16 @@ public void StopOboeBridge()
8786

8887
[MethodImpl(MethodImplOptions.NoInlining)]
8988
public static bool SetThreadAffinity(int coreMask) => OboeAudioBridge.nSetThreadAffinity(coreMask) != 0;
89+
90+
[MethodImpl(MethodImplOptions.NoInlining)]
91+
public bool IsOboeActive() => (oboeBridge as OboeAudioBridge)?.IsActive ?? false;
92+
93+
[MethodImpl(MethodImplOptions.NoInlining)]
94+
public string GetOboeStatus()
95+
{
96+
if (oboeBridge is not OboeAudioBridge bridge) return string.Empty;
97+
return $"{(bridge.IsAAudio ? "AAudio" : "OpenSLES")} [{(bridge.IsMMap ? "MMAP" : "Legacy")}]";
98+
}
9099
public double GetMeasuredAudioLatencyMs()
91100
{
92101
return (oboeBridge as OboeAudioBridge)?.GetOutputLatencyMs() ?? -1;

osu.Android/Native/OboeAudioBridge.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,5 @@ public void Dispose()
338338
[DllImport(lib_name)]
339339
private static extern void nOboeSetProvider(IntPtr ptr, IntPtr provider);
340340
[DllImport(lib_name)] internal static extern byte nSetThreadAffinity(int coreMask);
341-
[DllImport(lib_name)] internal static extern IntPtr nADPFCreateSession(long targetDurationNanos);
342-
[DllImport(lib_name)] internal static extern void nADPFReportActualDuration(IntPtr sessionPtr, long actualDurationNanos);
343-
[DllImport(lib_name)] internal static extern void nADPFUpdateTargetDuration(IntPtr sessionPtr, long targetDurationNanos);
344-
[DllImport(lib_name)] internal static extern void nADPFCloseSession(IntPtr sessionPtr);
345341
}
346342
}

osu.Android/Native/VulkanProbe.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static VulkanProbe()
4747
public bool SupportsGlobalPriority => !disposed && nativePtr != IntPtr.Zero && nVulkanSupportsGlobalPriority(nativePtr) != 0;
4848
public bool SupportsMemoryBudget => !disposed && nativePtr != IntPtr.Zero && nVulkanSupportsMemoryBudget(nativePtr) != 0;
4949

50-
public bool IsRecommended => IsAvailable && MeetsVulkan13 && SupportsDynamicRendering && SupportsSynchronization2;
50+
public bool IsRecommended => IsAvailable && MeetsVulkan13 && SupportsDynamicRendering && SupportsSynchronization2 && SupportsGraphicsPipelineLibrary && SupportsShaderObject;
5151

5252
public void Dispose()
5353
{

osu.Android/Native/oboe_bridge.cpp

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ void OboeBridge::setProvider(OboeAudioProvider provider) {
172172
oboe::DataCallbackResult OboeBridge::onAudioReady(
173173
oboe::AudioStream* stream, void* audioData, int32_t numFrames) {
174174

175-
176175
OboeAudioProvider provider = provider_.load(std::memory_order_acquire);
177176

178177
if (provider) {
@@ -190,7 +189,6 @@ oboe::DataCallbackResult OboeBridge::onAudioReady(
190189
memset(audioData, 0, byteCount);
191190
}
192191

193-
194192
uint32_t count = callbackCount_.fetch_add(1, std::memory_order_relaxed);
195193

196194
if ((count & 127) == 0) {
@@ -386,34 +384,4 @@ OSU_EXPORT byte nSetThreadAffinity(int coreMask) {
386384
}
387385
return (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) == 0) ? 1 : 0;
388386
}
389-
}
390-
391-
#include <android/performance_hint.h>
392-
393-
extern "C" {
394-
OSU_EXPORT intptr_t nADPFCreateSession(int64_t targetDurationNanos) {
395-
auto manager = APerformanceHint_getManager();
396-
if (!manager) return 0;
397-
398-
int32_t thread_id = gettid();
399-
return reinterpret_cast<intptr_t>(APerformanceHint_createSession(manager, &thread_id, 1, targetDurationNanos));
400-
}
401-
402-
OSU_EXPORT void nADPFReportActualDuration(intptr_t sessionPtr, int64_t actualDurationNanos) {
403-
if (sessionPtr) {
404-
APerformanceHint_reportActualWorkDuration(reinterpret_cast<APerformanceHintSession*>(sessionPtr), actualDurationNanos);
405-
}
406-
}
407-
408-
OSU_EXPORT void nADPFUpdateTargetDuration(intptr_t sessionPtr, int64_t targetDurationNanos) {
409-
if (sessionPtr) {
410-
APerformanceHint_updateTargetWorkDuration(reinterpret_cast<APerformanceHintSession*>(sessionPtr), targetDurationNanos);
411-
}
412-
}
413-
414-
OSU_EXPORT void nADPFCloseSession(intptr_t sessionPtr) {
415-
if (sessionPtr) {
416-
APerformanceHint_closeSession(reinterpret_cast<APerformanceHintSession*>(sessionPtr));
417-
}
418-
}
419-
}
387+
}

osu.Android/Native/vulkan_bridge.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,12 @@ void VulkanProbe::queryQueueFamilies(VkPhysicalDevice device) {
130130
}
131131

132132
void VulkanProbe::queryMailboxSupport(VkPhysicalDevice device) {
133-
// mailbox detection via display_timing hint
134133
uint32_t count = 0;
135134
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, nullptr);
136135
std::vector<VkExtensionProperties> exts(count);
137136
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, exts.data());
138137
for (const auto& ext : exts) {
139-
if (strcmp(ext.extensionName, "VK_GOOGLE_display_timing") == 0) {
138+
if (strcmp(ext.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) {
140139
deviceInfo_.supportsMailboxPresentMode = true;
141140
break;
142141
}

osu.Android/OsuGameAndroid.cs

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ public partial class OsuGameAndroid : OsuGame
8686
private OboeAudioRedirector? audioRedirector;
8787
private Delegate? activeMixersHandler;
8888
private object? activeMixersList;
89-
private IntPtr updateAdpfSession;
90-
private IntPtr renderAdpfSession;
9189

9290
/// <summary>
9391
/// Boxed reference to the native bridge manager.
@@ -211,24 +209,22 @@ protected override void LoadComplete()
211209
try
212210
{
213211
// Target 1ms (1,000,000ns) for 1000 FPS target.
214-
updateAdpfSession = OboeAudioBridge.nADPFCreateSession(1000000);
212+
215213

216214
Scheduler.Add(() =>
217215
{
218216
Host.DrawThread.Scheduler.Add(() =>
219217
{
220218
try
221219
{
222-
renderAdpfSession = OboeAudioBridge.nADPFCreateSession(1000000);
223220

224-
if (renderAdpfSession != IntPtr.Zero)
221+
225222
Debug.WriteLine("[osu!] ADPF Performance Hint Session created for Render thread");
226223
}
227224
catch { }
228225
});
229226
});
230227

231-
if (updateAdpfSession != IntPtr.Zero)
232228
Debug.WriteLine("[osu!] ADPF Performance Hint Session created for Update thread");
233229
}
234230
catch { }
@@ -267,9 +263,6 @@ protected override void LoadComplete()
267263
{
268264
startOboeBridge(latency =>
269265
{
270-
if (Math.Abs(audioOffset.Value) >= 0.01)
271-
return;
272-
273266
double suggested = Math.Clamp(-latency, audioOffset.MinValue, audioOffset.MaxValue);
274267
audioOffset.Value = suggested;
275268
Debug.WriteLine($"[osu!] Audio offset auto-suggested: {suggested:F1}ms (hardware latency={latency:F1}ms)");
@@ -404,6 +397,12 @@ private void selectHighestRefreshRate()
404397

405398
public override bool IsVulkanSupported => (nativeBridges as AndroidNativeBridgeManager)?.IsVulkanAvailable() ?? false;
406399

400+
public override bool IsOboeActive => (nativeBridges as AndroidNativeBridgeManager)?.IsOboeActive() ?? false;
401+
402+
public override string OboeStatus => (nativeBridges as AndroidNativeBridgeManager)?.GetOboeStatus() ?? string.Empty;
403+
404+
public override double OboeLatency => (nativeBridges as AndroidNativeBridgeManager)?.GetMeasuredAudioLatencyMs() ?? -1;
405+
407406
private void onActiveMixersChanged(object? sender, NotifyCollectionChangedEventArgs args) => Schedule(() => { if (lowLatencyAudio.Value) audioRedirector?.RefreshMixers(0); });
408407

409408
public double GetMeasuredAudioLatencyMs() => getMeasuredAudioLatencyFromBridge();
@@ -543,37 +542,11 @@ protected override void Dispose(bool isDisposing)
543542

544543
if (nativeBridges != null)
545544
disposeNativeBridges();
546-
547-
if (updateAdpfSession != IntPtr.Zero)
548-
{
549-
OboeAudioBridge.nADPFCloseSession(updateAdpfSession);
550-
updateAdpfSession = IntPtr.Zero;
551-
}
552-
553-
if (renderAdpfSession != IntPtr.Zero)
554-
{
555-
OboeAudioBridge.nADPFCloseSession(renderAdpfSession);
556-
renderAdpfSession = IntPtr.Zero;
557-
}
558545
}
559546
}
560547

561548
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
562-
protected override void UpdateAfterChildren()
563-
{
564-
if (updateAdpfSession == IntPtr.Zero)
565-
{
566-
base.UpdateAfterChildren();
567-
return;
568-
}
569-
570-
long startTime = Stopwatch.GetTimestamp();
571-
base.UpdateAfterChildren();
572-
long elapsedTicks = Stopwatch.GetTimestamp() - startTime;
573-
long elapsedNanos = (elapsedTicks * 1000000000) / Stopwatch.Frequency;
574-
575-
OboeAudioBridge.nADPFReportActualDuration(updateAdpfSession, elapsedNanos);
576-
}
549+
protected override void UpdateAfterChildren() => base.UpdateAfterChildren();
577550

578551
}
579552

osu.Game.OsuGameBase.patch

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--- a/osu.Game/OsuGameBase.cs
2+
+++ b/osu.Game/OsuGameBase.cs
3+
@@ -122,6 +122,12 @@
4+
5+
public virtual bool IsVulkanSupported => false;
6+
7+
+ public virtual bool IsOboeActive => false;
8+
+
9+
+ public virtual string OboeStatus => string.Empty;
10+
+
11+
+ public virtual double OboeLatency => -1;
12+
+
13+
public virtual string Version
14+
{
15+
get

osu.Game/Graphics/UserInterface/FPSCounter.cs

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

62+
[Resolved]
63+
protected OsuGame Game { get; private set; } = null!;
64+
65+
[Resolved]
66+
private GameHost host { get; set; } = null!;
67+
6268
public FPSCounter()
6369
{
6470
AutoSizeAxes = Axes.Both;
@@ -232,7 +238,17 @@ private void requestDisplay()
232238
private void updateFpsDisplay()
233239
{
234240
counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS);
235-
counterDrawFPS.Text = $"{displayedFpsCount:#,0} fps";
241+
string status = $"{displayedFpsCount:#,0} fps";
242+
243+
if (Game is OsuGame osu)
244+
{
245+
status += $" | {host.ResolvedRenderer.ToString()}";
246+
247+
if (osu.IsOboeActive)
248+
status += $" | Oboe: {osu.OboeStatus} ({osu.OboeLatency:F1}ms)";
249+
}
250+
251+
counterDrawFPS.Text = status;
236252
}
237253

238254
private void updateFrameTimeDisplay()

osu.Game/OsuGameBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ public virtual EndpointConfiguration CreateEndpoints() =>
122122

123123
public virtual bool IsVulkanSupported => false;
124124

125+
public virtual bool IsOboeActive => false;
126+
127+
public virtual string OboeStatus => string.Empty;
128+
129+
public virtual double OboeLatency => -1;
130+
125131
public virtual string Version
126132
{
127133
get

0 commit comments

Comments
 (0)