Skip to content

Commit 3e8b3f1

Browse files
authored
Merge pull request #143 from winnerspiros/fix/android-gameplay-regressions-and-optimizations-10080506137969536493
Revert gameplay regressions and optimize Android threading
2 parents 30fa033 + 39e61c3 commit 3e8b3f1

8 files changed

Lines changed: 81 additions & 107 deletions

File tree

osu.Android/AndroidNativeBridgeManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ internal sealed class AndroidNativeBridgeManager : IDisposable
1717
private object? oboeBridge;
1818
private object? vulkanProbe;
1919
private volatile bool disposed;
20+
private string? cachedOboeStatus;
21+
private string? cachedVulkanStatus;
2022

2123
[MethodImpl(MethodImplOptions.NoInlining)]
2224
public void StartOboeBridge(Scheduler scheduler, Action<double> onLatencyMeasured, IntPtr provider, int sampleRate = 0, Action<int>? onStarted = null)

osu.Android/Native/OboeAudioBridge.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Runtime.CompilerServices;
12
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
23
// See the LICENCE file in the repository root for full licence text.
34

@@ -79,8 +80,9 @@ public double GetOutputLatencyMs()
7980
catch { return -1; }
8081
}
8182

82-
public bool IsActive
83+
public bool IsActive
8384
{
85+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8486
get
8587
{
8688
if (disposed || nativePtr == IntPtr.Zero) return false;
@@ -119,8 +121,9 @@ public int BufferSizeInFrames
119121
}
120122
}
121123

122-
public bool IsAAudio
124+
public bool IsAAudio
123125
{
126+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
124127
get
125128
{
126129
if (disposed || nativePtr == IntPtr.Zero) return false;
@@ -129,8 +132,9 @@ public bool IsAAudio
129132
}
130133
}
131134

132-
public bool IsMMap
135+
public bool IsMMap
133136
{
137+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
134138
get
135139
{
136140
if (disposed || nativePtr == IntPtr.Zero) return false;
@@ -174,5 +178,9 @@ public void Dispose()
174178
[DllImport(lib_name)] private static extern byte nOboeIsMMap(IntPtr ptr);
175179
[DllImport(lib_name)] private static extern void nOboeSetProvider(IntPtr ptr, IntPtr provider);
176180
[DllImport(lib_name)] internal static extern byte nSetThreadAffinity(int coreMask);
181+
[DllImport(lib_name)] internal static extern IntPtr nADPFCreateSession(long targetDurationNanos);
182+
[DllImport(lib_name)] internal static extern void nADPFReportActualDuration(IntPtr sessionPtr, long actualDurationNanos);
183+
[DllImport(lib_name)] internal static extern void nADPFUpdateTargetDuration(IntPtr sessionPtr, long targetDurationNanos);
184+
[DllImport(lib_name)] internal static extern void nADPFCloseSession(IntPtr sessionPtr);
177185
}
178186
}

osu.Android/Native/oboe_bridge.cpp

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,17 @@ bool OboeBridge::open(int32_t sampleRate) {
5555
->setFormatConversionAllowed(false)
5656
->setCallback(stabilizedCallback_.get());
5757

58-
LOGI("Oboe stream: attempting open in Exclusive mode with AAudio (sampleRate=%d)", sampleRate);
5958
oboe::Result result = builder.openStream(stream_);
6059

6160
if (result != oboe::Result::OK) {
62-
LOGE("Failed to open Oboe stream in Exclusive mode (%s), falling back to Shared mode", oboe::convertToText(result));
63-
builder.setSharingMode(oboe::SharingMode::Shared);
64-
// If falling back to Shared, allow conversion to be more resilient
65-
builder.setChannelConversionAllowed(true)
66-
->setFormatConversionAllowed(true)
67-
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium);
68-
result = builder.openStream(stream_);
69-
}
70-
71-
if (result != oboe::Result::OK) {
72-
LOGE("Failed to open Oboe stream with AAudio API (%s), falling back to Unspecified API",
61+
LOGE("AAudio open failed (%s), falling back to unspecified API",
7362
oboe::convertToText(result));
7463
builder.setAudioApi(oboe::AudioApi::Unspecified);
7564
result = builder.openStream(stream_);
7665
}
7766

7867
if (result != oboe::Result::OK) {
79-
LOGE("Failed to open Oboe stream with any configuration: %s", oboe::convertToText(result));
68+
LOGE("Failed to open Oboe stream: %s", oboe::convertToText(result));
8069
return false;
8170
}
8271

@@ -183,6 +172,7 @@ void OboeBridge::setProvider(OboeAudioProvider provider) {
183172
oboe::DataCallbackResult OboeBridge::onAudioReady(
184173
oboe::AudioStream* stream, void* audioData, int32_t numFrames) {
185174

175+
186176
OboeAudioProvider provider = provider_.load(std::memory_order_acquire);
187177

188178
if (provider) {
@@ -200,6 +190,7 @@ oboe::DataCallbackResult OboeBridge::onAudioReady(
200190
memset(audioData, 0, byteCount);
201191
}
202192

193+
203194
uint32_t count = callbackCount_.fetch_add(1, std::memory_order_relaxed);
204195

205196
if ((count & 127) == 0) {
@@ -396,3 +387,33 @@ OSU_EXPORT byte nSetThreadAffinity(int coreMask) {
396387
return (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) == 0) ? 1 : 0;
397388
}
398389
}
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+
}

osu.Android/OsuGameAndroid.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,12 @@ protected override void LoadComplete()
189189
// Dispatch to the draw thread to pin it.
190190
Host.DrawThread.Scheduler.Add(() =>
191191
{
192-
try
193-
{
194-
if (OboeAudioBridge.nSetThreadAffinity(0xF8) != 0)
195-
Debug.WriteLine("[osu!] Render thread pinned to big cores");
196-
}
197-
catch { }
192+
try { if (OboeAudioBridge.nSetThreadAffinity(0xF8) != 0) Debug.WriteLine("[osu!] Render thread pinned to big cores"); } catch { }
193+
});
194+
195+
Host.InputThread.Scheduler.Add(() =>
196+
{
197+
try { if (OboeAudioBridge.nSetThreadAffinity(0xF8) != 0) Debug.WriteLine("[osu!] Input thread pinned to big cores"); } catch { }
198198
});
199199
});
200200
}

osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@
33

44
using osu.Framework.Allocation;
55
using osu.Framework.Graphics;
6-
using osu.Framework;
7-
86
using osu.Game.Configuration;
97
using osu.Game.Rulesets.Judgements;
108
using osu.Game.Rulesets.Objects.Drawables;
119
using osu.Game.Rulesets.Scoring;
12-
13-
using osuTK.Graphics;
1410
using osuTK;
11+
using osuTK.Graphics;
1512

1613
namespace osu.Game.Rulesets.Osu.Objects.Drawables
1714
{
@@ -81,18 +78,9 @@ protected override void ApplyHitAnimations()
8178

8279
if (hitLightingEnabled)
8380
{
84-
if (RuntimeInfo.OS == RuntimeInfo.Platform.Android)
85-
{
86-
// Simplified animation for Android to reduce render load
87-
Lighting.ScaleTo(1.0f).ScaleTo(1.1f, 400, Easing.Out);
88-
Lighting.FadeIn(150).Then().Delay(100).FadeOut(600);
89-
}
90-
else
91-
{
92-
// todo: this animation changes slightly based on new/old legacy skin versions.
93-
Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
94-
Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
95-
}
81+
// todo: this animation changes slightly based on new/old legacy skin versions.
82+
Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
83+
Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
9684

9785
// extend the lifetime to cover lighting fade
9886
LifetimeEnd = Lighting.LatestTransformEndTime;
@@ -125,4 +113,4 @@ public override void PlayAnimation()
125113
}
126114
}
127115
}
128-
}
116+
}

osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,24 @@
33

44
#nullable disable
55

6+
using System;
67
using System.Collections.Generic;
78
using System.Linq;
8-
using System;
9-
9+
using JetBrains.Annotations;
1010
using osu.Framework.Allocation;
1111
using osu.Framework.Bindables;
12-
using osu.Framework.Graphics.Containers;
1312
using osu.Framework.Graphics;
13+
using osu.Framework.Graphics.Containers;
1414
using osu.Framework.Layout;
15-
15+
using osu.Game.Audio;
1616
using osu.Game.Graphics.Containers;
1717
using osu.Game.Rulesets.Judgements;
18-
using osu.Game.Rulesets.Objects.Drawables;
1918
using osu.Game.Rulesets.Objects;
19+
using osu.Game.Rulesets.Objects.Drawables;
2020
using osu.Game.Rulesets.Osu.Judgements;
2121
using osu.Game.Rulesets.Osu.Skinning.Default;
2222
using osu.Game.Rulesets.Scoring;
2323
using osu.Game.Skinning;
24-
25-
using JetBrains.Annotations;
2624
using osuTK;
2725

2826
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@@ -166,8 +164,8 @@ protected override void LoadSamples()
166164
{
167165
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
168166

169-
Samples.Samples = HitObject.TailSamples.ToArray();
170-
slidingSample.Samples = HitObject.CreateSlidingSamples().ToArray();
167+
Samples.Samples = HitObject.TailSamples.Cast<ISampleInfo>().ToArray();
168+
slidingSample.Samples = HitObject.CreateSlidingSamples().Cast<ISampleInfo>().ToArray();
171169
}
172170

173171
public override void StopAllSamples()
@@ -303,13 +301,7 @@ protected override void CheckForResult(bool userTriggered, double timeOffset)
303301
ApplyResult(static (r, hitObject) =>
304302
{
305303
int totalTicks = hitObject.NestedHitObjects.Count;
306-
int hitTicks = 0;
307-
308-
foreach (var nested in hitObject.NestedHitObjects)
309-
{
310-
if (nested.IsHit)
311-
hitTicks++;
312-
}
304+
int hitTicks = hitObject.NestedHitObjects.Count(h => h.IsHit);
313305

314306
if (hitTicks == totalTicks)
315307
r.Type = HitResult.Great;
@@ -328,18 +320,7 @@ protected override void CheckForResult(bool userTriggered, double timeOffset)
328320
// But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc).
329321
ApplyResult(static (r, hitObject) =>
330322
{
331-
bool anyHit = false;
332-
333-
foreach (var nested in hitObject.NestedHitObjects)
334-
{
335-
if (nested.Result.IsHit)
336-
{
337-
anyHit = true;
338-
break;
339-
}
340-
}
341-
342-
r.Type = anyHit ? r.Judgement.MaxResult : r.Judgement.MinResult;
323+
r.Type = hitObject.NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult;
343324
});
344325
}
345326
}
@@ -429,4 +410,4 @@ internal void RestoreHitAnimations()
429410

430411
#endregion
431412
}
432-
}
413+
}

osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22
// See the LICENCE file in the repository root for full licence text.
33

4-
using System.Collections.Generic;
54
using System;
6-
5+
using System.Collections.Generic;
76
using osu.Framework.Allocation;
87
using osu.Framework.Bindables;
98
using osu.Framework.Graphics;
10-
using osu.Framework;
11-
129
using osu.Game.Rulesets.Objects.Drawables;
1310
using osu.Game.Rulesets.Objects.Types;
14-
using osu.Game.Rulesets.Osu.Objects.Drawables;
1511
using osu.Game.Rulesets.Osu.Objects;
16-
12+
using osu.Game.Rulesets.Osu.Objects.Drawables;
1713
using osuTK;
1814

1915
namespace osu.Game.Rulesets.Osu.Skinning
@@ -23,7 +19,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
2319
/// </summary>
2420
public abstract partial class SnakingSliderBody : SliderBody, ISliderProgress
2521
{
26-
private double lastUpdateTime;
2722
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
2823

2924
public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
@@ -151,23 +146,6 @@ private void setRange(double p0, double p1)
151146
(p0, p1) = (p1, p0);
152147

153148
if (SnakedStart == p0 && SnakedEnd == p1) return;
154-
#if DEBUG
155-
const bool is_debug = true;
156-
#else
157-
const bool is_debug = false;
158-
#endif
159-
160-
if (RuntimeInfo.OS == RuntimeInfo.Platform.Android && !is_debug)
161-
{
162-
// Throttle updates on Android to save CPU/GPU cycles during snaking.
163-
// We only update every 2nd frame if the progress delta is small.
164-
if (lastUpdateTime > 0 && Clock.CurrentTime - lastUpdateTime < 16)
165-
{
166-
double delta = Math.Max(Math.Abs(p0 - (SnakedStart ?? 0)), Math.Abs(p1 - (SnakedEnd ?? 0)));
167-
if (delta < 0.0001) return;
168-
}
169-
lastUpdateTime = Clock.CurrentTime;
170-
}
171149

172150
SnakedStart = p0;
173151
SnakedEnd = p1;
@@ -184,4 +162,4 @@ private void setRange(double p0, double p1)
184162
Path.Position = snakedPosition - Path.PositionInBoundingBox(Vector2.Zero);
185163
}
186164
}
187-
}
165+
}

0 commit comments

Comments
 (0)