Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

- name: Install .NET 10.0.x
uses: actions/setup-dotnet@v5
Expand Down Expand Up @@ -81,6 +82,7 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

- name: Install .NET 10.0.x
uses: actions/setup-dotnet@v5
Expand Down Expand Up @@ -150,6 +152,7 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

- name: Setup JDK 11
uses: actions/setup-java@v5
Expand Down Expand Up @@ -177,6 +180,7 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

- name: Install .NET 10.0.x
uses: actions/setup-dotnet@v5
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

- name: Setup JDK 17
uses: actions/setup-java@v5
Expand Down Expand Up @@ -54,7 +55,7 @@ jobs:
"$CMAKE_BIN" -B "build-native/$ABI" -S osu.Android/Native \
-DCMAKE_TOOLCHAIN_FILE="$NDK_HOME/build/cmake/android.toolchain.cmake" \
-DANDROID_ABI="$ABI" \
-DANDROID_PLATFORM=android-33 \
-DANDROID_PLATFORM=android-36 \
-DCMAKE_BUILD_TYPE=Release
"$CMAKE_BIN" --build "build-native/$ABI" --config Release -j "$(nproc)"
mkdir -p "osu.Android/libs/$ABI"
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "submodules/osu-framework"]
path = submodules/osu-framework
url = https://github.com/winnerspiros/osu-framework.git
7 changes: 6 additions & 1 deletion osu.Android.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<!-- Enable optimized marshal methods for faster managed↔native interop.
.NET 10 generates efficient bridging code at build time instead of the
slower runtime-generated trampolines. -->
<AndroidEnableMarshalMethods>true</AndroidEnableMarshalMethods>
<!-- Suppress XA0141 page-size warnings. ELF patching is disabled for all configurations
(see PatchElfPageSizeEnabled below) to prevent APK signature corruption.
The XA0141 warning is therefore expected and harmless.
Expand Down Expand Up @@ -67,7 +71,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.416.0" />
<!-- Use winnerspiros/osu-framework fork (net10.0-android, optimized) via submodule instead of ppy NuGet package -->
<ProjectReference Include="$(MSBuildThisFileDirectory)submodules\osu-framework\osu.Framework.Android\osu.Framework.Android.csproj" />
</ItemGroup>

<PropertyGroup>
Expand Down
7 changes: 6 additions & 1 deletion osu.Android.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
"osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj",
"osu.Game.Tests.Android\\osu.Game.Tests.Android.csproj",
"osu.Game.Tests\\osu.Game.Tests.csproj",
"osu.Game\\osu.Game.csproj"
"osu.Game\\osu.Game.csproj",
"submodules\\osu-framework\\osu.Framework\\osu.Framework.csproj",
"submodules\\osu-framework\\osu.Framework.Android\\osu.Framework.Android.csproj",
"submodules\\osu-framework\\submodules\\veldrid\\src\\Veldrid\\Veldrid.csproj",
"submodules\\osu-framework\\submodules\\veldrid\\src\\Veldrid.MetalBindings\\Veldrid.MetalBindings.csproj",
"submodules\\osu-framework\\submodules\\veldrid\\src\\Veldrid.OpenGLBindings\\Veldrid.OpenGLBindings.csproj"
]
}
}
21 changes: 13 additions & 8 deletions osu.Android/AndroidImportTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ public override void DeleteFile()
{
// there are more performant overloads of this method, but this one is the most backwards-compatible
// (dates back to API 1).
string filename;

var cursor = contentResolver.Query(uri, null, null, null, null);

if (cursor == null)
return null;
using (var cursor = contentResolver.Query(uri, null, null, null, null))
{
if (cursor == null)
return null;

if (!cursor.MoveToFirst())
return null;
if (!cursor.MoveToFirst())
return null;

int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName);
string filename = cursor.GetString(filenameColumn) ?? uri.Path ?? string.Empty;
int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName);
filename = cursor.GetString(filenameColumn) ?? uri.Path ?? string.Empty;
}

// SharpCompress requires archive streams to be seekable, which the stream opened by
// OpenInputStream() seems to not necessarily be.
Expand All @@ -52,7 +54,10 @@ public override void DeleteFile()
using (var stream = contentResolver.OpenInputStream(uri))
{
if (stream == null)
{
copy.Dispose();
return null;
}

await stream.CopyToAsync(copy).ConfigureAwait(false);
}
Expand Down
10 changes: 8 additions & 2 deletions osu.Android/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="36" />
<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">
<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" android:extractNativeLibs="false">
<provider android:name="androidx.core.content.FileProvider" android:authorities="sh.ppy.osulazer.fileprovider" android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
</provider>
<meta-data android:name="com.samsung.android.keepalive.density" android:value="true" /><meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true" /></application>
<meta-data android:name="com.samsung.android.keepalive.density" android:value="true" />
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true" />
<!-- Register as a game for Samsung Game Launcher / Game Booster.
This enables vendor-specific optimizations (performance boost, thermal management,
display refresh rate priority) on Samsung devices automatically. -->
<meta-data android:name="com.samsung.android.game.biz" android:value="true" />
</application>
</manifest>
33 changes: 25 additions & 8 deletions osu.Android/AndroidNativeBridgeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,20 @@ public void StartOboeBridge(Scheduler scheduler, Action<double> onLatencyMeasure
if (provider != IntPtr.Zero)
bridge.SetProvider(provider);

// Calculate dynamic big-core mask for audio thread, matching the pattern in OsuGameAndroid.LoadComplete
int audioAffinityMask;
int cores = System.Environment.ProcessorCount;
int bigStart = Math.Max(cores / 2, 1);
audioAffinityMask = 0;
// Use sysfs-based CPU topology for smart big-core detection.
// Falls back to generic upper-half heuristic if native library unavailable.
int audioAffinityMask = GetBigCoreMask();

for (int i = bigStart; i < Math.Min(cores, 32); i++)
audioAffinityMask |= 1 << i;
if (audioAffinityMask == 0)
{
int cores = System.Environment.ProcessorCount;
int bigStart = Math.Max(cores / 2, 1);

for (int i = bigStart; i < Math.Min(cores, 32); i++)
audioAffinityMask |= 1 << i;

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

try { SetThreadAffinity(audioAffinityMask); }
catch (Exception e) { Debug.WriteLine($"[osu!] Audio thread affinity failed: {e.Message}"); }
Expand Down Expand Up @@ -114,6 +118,19 @@ public void StopOboeBridge()
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool SetThreadAffinity(int coreMask) => OboeAudioBridge.nSetThreadAffinity(coreMask) != 0;

/// <summary>
/// Returns a bitmask of high-performance CPU cores detected via sysfs topology.
/// Uses /sys/devices/system/cpu/cpuN/cpufreq/cpuinfo_max_freq to identify cores
/// whose max frequency is >= 70% of the fastest core (Prime + Gold on big.LITTLE SoCs).
/// Returns 0 if sysfs is unavailable; callers should use a fallback heuristic.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static int GetBigCoreMask()
{
try { return OboeAudioBridge.nGetBigCoreMask(); }
catch { return 0; }
}

[MethodImpl(MethodImplOptions.NoInlining)]
public bool IsOboeActive() => (oboeBridge as OboeAudioBridge)?.IsActive ?? false;

Expand Down
8 changes: 5 additions & 3 deletions osu.Android/Input/AndroidKeyboardHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Frozen;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Android.Views;
Expand All @@ -16,8 +17,9 @@ public class AndroidKeyboardHandler : InputHandler
public override string Description => "Keyboard (Low Latency)";
public override bool IsActive => Enabled.Value;

// Static dictionary for O(1) key mapping instead of 80+ case switch.
private static readonly Dictionary<Keycode, Key> key_map = new Dictionary<Keycode, Key>
// FrozenDictionary for maximum-performance O(1) key mapping.
// Built once at startup; faster than Dictionary for read-only lookups.
private static readonly FrozenDictionary<Keycode, Key> key_map = new Dictionary<Keycode, Key>
{
{ Keycode.A, Key.A }, { Keycode.B, Key.B }, { Keycode.C, Key.C }, { Keycode.D, Key.D },
{ Keycode.E, Key.E }, { Keycode.F, Key.F }, { Keycode.G, Key.G }, { Keycode.H, Key.H },
Expand Down Expand Up @@ -50,7 +52,7 @@ public class AndroidKeyboardHandler : InputHandler
{ Keycode.Backslash, Key.BackSlash }, { Keycode.Semicolon, Key.Semicolon },
{ Keycode.Apostrophe, Key.Quote }, { Keycode.Comma, Key.Comma },
{ Keycode.Period, Key.Period }, { Keycode.Slash, Key.Slash },
};
}.ToFrozenDictionary();

public AndroidKeyboardHandler()
{
Expand Down
8 changes: 6 additions & 2 deletions osu.Android/Native/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
cmake_minimum_required(VERSION 3.18)
project(osu_native LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Maximum release optimizations for lowest-latency audio callback path.
# -O3: aggressive inlining & vectorisation
Expand All @@ -11,7 +12,10 @@ set(CMAKE_CXX_STANDARD 17)
# -fvisibility=hidden: only OSU_EXPORT symbols are visible
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -ffast-math -ffunction-sections -fdata-sections -fvisibility=hidden -DNDEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -ffast-math -ffunction-sections -fdata-sections -fvisibility=hidden -DNDEBUG")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "-Wl,--gc-sections -s")
# -Wl,-z,max-page-size=16384: align ELF LOAD segments to 16 KB for Android 15+
# devices with 16 KB page sizes. Without this, the .so will fail to load on
# such devices. NDK r28+ supports this flag.
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "-Wl,--gc-sections -Wl,-z,max-page-size=16384 -s")

# Disable Oboe's flowgraph module — we don't use any audio processing/conversion
# features (our bridge outputs silence for latency measurement only).
Expand Down
1 change: 1 addition & 0 deletions osu.Android/Native/OboeAudioBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ public void Dispose()
[DllImport(lib_name)] private static extern void nOboeSetProvider(IntPtr ptr, IntPtr provider);
[DllImport(lib_name)] private static extern IntPtr nOboeGetLastErrorMessage(IntPtr ptr);
[DllImport(lib_name)] internal static extern byte nSetThreadAffinity(int coreMask);
[DllImport(lib_name)] internal static extern int nGetBigCoreMask();
[DllImport(lib_name)] internal static extern IntPtr nADPFCreateSession(long targetDurationNanos);
[DllImport(lib_name)] internal static extern void nADPFReportActualDuration(IntPtr sessionPtr, long actualDurationNanos);
[DllImport(lib_name)] internal static extern void nADPFUpdateTargetDuration(IntPtr sessionPtr, long targetDurationNanos);
Expand Down
Loading
Loading