diff --git a/osu.Android/Native/CMakeLists.txt b/osu.Android/Native/CMakeLists.txt index 24f7bc4efd23..4d1f8e3585d6 100644 --- a/osu.Android/Native/CMakeLists.txt +++ b/osu.Android/Native/CMakeLists.txt @@ -22,24 +22,40 @@ set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "-Wl,--gc-sections -Wl,-z,max-page-size=16 # features (our bridge outputs silence for latency measurement only). # This reduces the Oboe portion of the binary by ~50%. set(OBOE_ENABLE_FLOWGRAPH OFF CACHE BOOL "Disable Oboe flowgraph to reduce binary size") +# Skip Oboe's tests/examples — we never ship them. Avoids pulling googletest +# and shaves several seconds off the cold CMake configure/build. +set(BUILD_TESTING OFF CACHE BOOL "Disable Oboe tests" FORCE) +set(OBOE_BUILD_TESTS OFF CACHE BOOL "Disable Oboe tests" FORCE) +set(OBOE_BUILD_EXAMPLES OFF CACHE BOOL "Disable Oboe examples" FORCE) +set(OBOE_BUILD_DOCS OFF CACHE BOOL "Disable Oboe docs" FORCE) # Download and build Oboe main branch from source to ensure we have the latest # features and fixes (ADPF performance hints, workload management, spatialization, # API 36 compatibility) regardless of the build environment. # main is preferred over pinned tags because Oboe releases infrequently (~yearly) # and the main branch accumulates significant latency-critical improvements between tags. +# GIT_SHALLOW TRUE: only fetch the tip of main (no history) — significantly faster CI checkout. include(FetchContent) FetchContent_Declare(oboe GIT_REPOSITORY https://github.com/google/oboe.git GIT_TAG main + GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(oboe) -# Patch Oboe's deprecated -Ofast flag to avoid build warnings/errors -# and ensure we use the same optimized flags as the rest of the project. +# Patch Oboe's compile options: +# 1. Replace deprecated `-Ofast` with `-O3` — `-Ofast` was removed in Clang 21 +# and emits warnings on newer NDKs. +# 2. Strip `-ffast-math` from Oboe's compile options. In a callback that only +# does memset/store it is harmless functionally, but it injects libm +# `__FINITE_MATH_ONLY__` symbol versions that can break linkage against the +# system libm in rare NDK combos. Our own bridge keeps `-ffast-math` (see +# CMAKE_CXX_FLAGS_RELEASE above) because we link only against libc/log/Vulkan. get_target_property(OBOE_OPTIONS oboe COMPILE_OPTIONS) if(OBOE_OPTIONS) - string(REPLACE "-Ofast" "-O3;-ffast-math" OBOE_OPTIONS "${OBOE_OPTIONS}") + list(REMOVE_ITEM OBOE_OPTIONS "-Ofast") + list(REMOVE_ITEM OBOE_OPTIONS "-ffast-math") + list(APPEND OBOE_OPTIONS "-O3") set_target_properties(oboe PROPERTIES COMPILE_OPTIONS "${OBOE_OPTIONS}") endif() diff --git a/osu.Android/Native/oboe_bridge.cpp b/osu.Android/Native/oboe_bridge.cpp index d14ce1172956..814ed330ee21 100644 --- a/osu.Android/Native/oboe_bridge.cpp +++ b/osu.Android/Native/oboe_bridge.cpp @@ -114,7 +114,7 @@ bool OboeBridge::open(int32_t sampleRate) { // Audio is pre-mixed by BASS — tell Android not to spatialize it again. ->setIsContentSpatialized(true) // Prevent other apps from capturing our audio stream (competitive integrity). - ->setAllowedCapturePolicy(oboe::AllowedCapturePolicy::AllowNone) + ->setAllowedCapturePolicy(oboe::AllowedCapturePolicy::None) // Use shared_ptr overload (non-deprecated) for data callback. ->setDataCallback(stabilizedCallback_) // Non-owning shared_ptr for error callback — OboeBridge outlives the stream. @@ -250,9 +250,9 @@ void OboeBridge::setProvider(OboeAudioProvider provider) { provider_.store(provider, std::memory_order_release); } -const char* OboeBridge::getLastError() const { +std::string OboeBridge::getLastError() const { std::lock_guard lock(errorLock_); - return lastError_.empty() ? nullptr : lastError_.c_str(); + return lastError_; } oboe::DataCallbackResult OboeBridge::onAudioReady( @@ -283,14 +283,19 @@ oboe::DataCallbackResult OboeBridge::onAudioReady( uint32_t count = callbackCount_.fetch_add(1, std::memory_order_relaxed); + // LatencyTuner once every 128 callbacks (~1.5s @ 192 burst, 48 kHz). + // updateLatency() issues an AAudio syscall, so throttle it further to every + // 256 callbacks — Tab still sees stable values, but we cut audio-thread + // syscall pressure in half. if ((count & 127) == 0) { - updateLatency(); - // Dynamically tune the buffer size to the lowest stable value. if (tuner_) { tuner_->tune(); } + if ((count & 255) == 0) + updateLatency(); + // Attempt to set CPU affinity to high-performance cores. // We do this inside the audio callback to ensure we target the AAudio thread. // Uses sysfs-based topology detection for accurate big-core identification @@ -478,7 +483,15 @@ OSU_EXPORT void nOboeSetProvider(intptr_t ptr, OboeAudioProvider provider) { OSU_EXPORT const char* nOboeGetLastErrorMessage(intptr_t ptr) { auto* bridge = reinterpret_cast(ptr); - return bridge ? bridge->getLastError() : nullptr; + if (!bridge) return nullptr; + + // Hold a thread_local snapshot so the pointer we hand back to managed code + // remains valid for the duration of the P/Invoke marshalling step, even if + // another thread (Oboe error callback) overwrites `lastError_` immediately + // after we return. Each managed thread gets its own buffer. + thread_local std::string snapshot; + snapshot = bridge->getLastError(); + return snapshot.empty() ? nullptr : snapshot.c_str(); } } // extern "C" @@ -512,6 +525,9 @@ OSU_EXPORT int nGetBigCoreMask() { } #include +// Explicit dependency for gettid() used in nADPFCreateSession below — do not +// rely on transitive includes from Oboe / NDK headers, which may change. +#include extern "C" { OSU_EXPORT intptr_t nADPFCreateSession(int64_t targetDurationNanos) { diff --git a/osu.Android/Native/oboe_bridge.h b/osu.Android/Native/oboe_bridge.h index 41bcb221385f..0bb9a3b4744d 100644 --- a/osu.Android/Native/oboe_bridge.h +++ b/osu.Android/Native/oboe_bridge.h @@ -33,7 +33,11 @@ class OboeBridge : public oboe::AudioStreamCallback { bool isAAudio() const; bool isMMap() const; void setProvider(OboeAudioProvider provider); - const char* getLastError() const; + /// Returns a copy of the most recent error message under lock. We return + /// by value (not a pointer to internal storage) so callers can't observe a + /// torn or freed `std::string` if another thread mutates `lastError_` + /// concurrently (Oboe error callbacks fire from an internal thread). + std::string getLastError() const; // oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(