#pragma once // Android frame-rate and CPU-clock plumbing for the live client's render loop. // // - display_refresh_rate(): the panel's refresh rate now (MainActivity.currentRefreshRate), and // render_rate(): the app-vsync rate the system actually gives the app (MainActivity.currentRenderRate), // for the perf log's refresh_hz / render_hz columns. // - request_frame_rate(): ANativeWindow_setFrameRate (API 30), the surface's preferred rate. The display // mode itself is chosen by MainActivity (--refresh-rate -> preferredDisplayModeId); the vendor // (ColorOS etc.) can still cap an app it does not know. // - PerformanceHint: ADPF APerformanceHint (API 33). The loop reports each frame's CPU work against the // frame budget, so the governor raises the clocks before a frame misses instead of idling the CPU at // its lowest step between short bursts (the 556-748 MHz seen in perf-20260929-153205.csv). // // Every NDK entry point is looked up at run time: minSdk is 24. On other platforms all of this is a no-op. #include #include #include #ifdef __ANDROID__ #include #include #include #include #endif namespace android_perf { #ifdef __ANDROID__ inline void* libandroid() { static void* lib = dlopen("libandroid.so", RTLD_NOW); return lib; } inline double call_activity_float(const char* name) { auto* env = static_cast(SDL_GetAndroidJNIEnv()); auto activity = static_cast(SDL_GetAndroidActivity()); if (!env || !activity) return -1.0; double hz = -1.0; jclass cls = env->GetObjectClass(activity); if (jmethodID method = env->GetMethodID(cls, name, "()F")) hz = env->CallFloatMethod(activity, method); if (env->ExceptionCheck()) { env->ExceptionClear(); hz = -1.0; } env->DeleteLocalRef(cls); env->DeleteLocalRef(activity); return hz; } inline double display_refresh_rate() { return call_activity_float("currentRefreshRate"); } // The app-vsync rate (Choreographer); -1 until the first measurement. inline double render_rate() { return call_activity_float("currentRenderRate"); } inline double battery_celsius() { return call_activity_float("batteryTemperature"); } // false when the call is unavailable (API < 30) or rejected. inline bool request_frame_rate(SDL_Window* window, float fps) { using SetFrameRate = int32_t (*)(ANativeWindow*, float, int8_t); static const auto set_frame_rate = libandroid() ? reinterpret_cast(dlsym(libandroid(), "ANativeWindow_setFrameRate")) : nullptr; if (!set_frame_rate || !window) return false; auto* native = static_cast(SDL_GetPointerProperty( SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr)); if (!native) return false; // ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT: a game, not fixed-rate video. return set_frame_rate(native, fps, 0) == 0; } class PerformanceHint { public: // `thread_ids`: the threads that do each frame's work (render thread + script thread). bool open(const std::vector& thread_ids, std::int64_t target_ns) { void* lib = libandroid(); if (!lib || thread_ids.empty()) return false; get_manager_ = reinterpret_cast(dlsym(lib, "APerformanceHint_getManager")); create_ = reinterpret_cast(dlsym(lib, "APerformanceHint_createSession")); update_target_ = reinterpret_cast(dlsym(lib, "APerformanceHint_updateTargetWorkDuration")); report_ = reinterpret_cast(dlsym(lib, "APerformanceHint_reportActualWorkDuration")); close_ = reinterpret_cast(dlsym(lib, "APerformanceHint_closeSession")); if (!get_manager_ || !create_ || !report_ || !close_) return false; void* manager = get_manager_(); if (!manager) return false; session_ = create_(manager, thread_ids.data(), thread_ids.size(), target_ns); target_ns_ = target_ns; return session_ != nullptr; } ~PerformanceHint() { if (session_ && close_) close_(session_); } bool active() const { return session_ != nullptr; } void set_target(std::int64_t target_ns) { if (!session_ || !update_target_ || target_ns == target_ns_ || target_ns <= 0) return; update_target_(session_, target_ns); target_ns_ = target_ns; } void report(std::int64_t actual_ns) { if (session_ && actual_ns > 0) report_(session_, actual_ns); } private: using GetManager = void* (*)(); using CreateSession = void* (*)(void*, const int32_t*, size_t, int64_t); using UpdateTarget = int (*)(void*, int64_t); using Report = int (*)(void*, int64_t); using Close = void (*)(void*); GetManager get_manager_ = nullptr; CreateSession create_ = nullptr; UpdateTarget update_target_ = nullptr; Report report_ = nullptr; Close close_ = nullptr; void* session_ = nullptr; std::int64_t target_ns_ = 0; }; inline int32_t current_thread_id() { return int32_t(gettid()); } #else inline double display_refresh_rate() { const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay()); return mode ? double(mode->refresh_rate) : -1.0; } inline double render_rate() { return display_refresh_rate(); } inline double battery_celsius() { return -1.0; } inline bool request_frame_rate(SDL_Window*, float) { return false; } class PerformanceHint { public: bool open(const std::vector&, std::int64_t) { return false; } bool active() const { return false; } void set_target(std::int64_t) {} void report(std::int64_t) {} }; inline int32_t current_thread_id() { return 0; } #endif } // namespace android_perf