/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "ChoreographerThread" #include #include #include "ChoreographerThread.h" #include "Thread.h" #include "CpuInfo.h" #include #include #include #include #include #include #include "ChoreographerShim.h" #include "Log.h" #include "Trace.h" namespace swappy { // AChoreographer is supported from API 24. To allow compilation for minSDK < 24 // and still use AChoreographer for SDK >= 24 we need runtime support to call // AChoreographer APIs. using PFN_AChoreographer_getInstance = AChoreographer* (*)(); using PFN_AChoreographer_postFrameCallback = void (*)(AChoreographer* choreographer, AChoreographer_frameCallback callback, void* data); using PFN_AChoreographer_postFrameCallbackDelayed = void (*)(AChoreographer* choreographer, AChoreographer_frameCallback callback, void* data, long delayMillis); class NDKChoreographerThread : public ChoreographerThread { public: NDKChoreographerThread(Callback onChoreographer); ~NDKChoreographerThread() override; private: void looperThread(); void scheduleNextFrameCallback() override REQUIRES(mWaitingMutex); PFN_AChoreographer_getInstance mAChoreographer_getInstance = nullptr; PFN_AChoreographer_postFrameCallback mAChoreographer_postFrameCallback = nullptr; PFN_AChoreographer_postFrameCallbackDelayed mAChoreographer_postFrameCallbackDelayed = nullptr; void *mLibAndroid = nullptr; std::thread mThread; std::condition_variable mWaitingCondition; ALooper *mLooper GUARDED_BY(mWaitingMutex) = nullptr; bool mThreadRunning GUARDED_BY(mWaitingMutex) = false; AChoreographer *mChoreographer GUARDED_BY(mWaitingMutex) = nullptr; }; NDKChoreographerThread::NDKChoreographerThread(Callback onChoreographer) : ChoreographerThread(onChoreographer) { mLibAndroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); if (mLibAndroid == nullptr) { ALOGE("FATAL: cannot open libandroid.so: %s", strerror(errno)); abort(); } mAChoreographer_getInstance = reinterpret_cast( dlsym(mLibAndroid, "AChoreographer_getInstance")); mAChoreographer_postFrameCallback = reinterpret_cast( dlsym(mLibAndroid, "AChoreographer_postFrameCallback")); mAChoreographer_postFrameCallbackDelayed = reinterpret_cast( dlsym(mLibAndroid, "AChoreographer_postFrameCallbackDelayed")); if (!mAChoreographer_getInstance || !mAChoreographer_postFrameCallback || !mAChoreographer_postFrameCallbackDelayed) { ALOGE("FATAL: cannot get AChoreographer symbols"); abort(); } std::unique_lock lock(mWaitingMutex); // create a new ALooper thread to get Choreographer events mThreadRunning = true; mThread = std::thread([this]() { looperThread(); }); mWaitingCondition.wait(lock, [&]() REQUIRES(mWaitingMutex) { return mChoreographer != nullptr; }); } NDKChoreographerThread::~NDKChoreographerThread() { ALOGI("Destroying NDKChoreographerThread"); if (mLibAndroid != nullptr) dlclose(mLibAndroid); if (!mLooper) { return; } ALooper_acquire(mLooper); mThreadRunning = false; ALooper_wake(mLooper); ALooper_release(mLooper); mThread.join(); } void NDKChoreographerThread::looperThread() { int outFd, outEvents; void *outData; std::lock_guard lock(mWaitingMutex); mLooper = ALooper_prepare(0); if (!mLooper) { ALOGE("ALooper_prepare failed"); return; } mChoreographer = mAChoreographer_getInstance(); if (!mChoreographer) { ALOGE("AChoreographer_getInstance failed"); return; } mWaitingCondition.notify_all(); const char *name = "SwappyChoreographer"; CpuInfo cpu; cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(0, &cpu_set); if (cpu.getNumberOfCpus() > 0) { ALOGI("Swappy found %d CPUs [%s].", cpu.getNumberOfCpus(), cpu.getHardware().c_str()); if (cpu.getNumberOfLittleCores() > 0) { cpu_set = cpu.getLittleCoresMask(); } } const auto tid = gettid(); ALOGI("Setting '%s' thread [%d-0x%x] affinity mask to 0x%x.", name, tid, tid, to_mask(cpu_set)); sched_setaffinity(tid, sizeof(cpu_set), &cpu_set); pthread_setname_np(pthread_self(), name); while (mThreadRunning) { // mutex should be unlocked before sleeping on pollAll mWaitingMutex.unlock(); ALooper_pollAll(-1, &outFd, &outEvents, &outData); mWaitingMutex.lock(); } ALOGI("Terminating Looper thread"); return; } void NDKChoreographerThread::scheduleNextFrameCallback() { AChoreographer_frameCallback frameCallback = [](long frameTimeNanos, void *data) { reinterpret_cast(data)->onChoreographer(); }; mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1); } class JavaChoreographerThread : public ChoreographerThread { public: JavaChoreographerThread(JavaVM *vm, Callback onChoreographer); ~JavaChoreographerThread() override; static void onChoreographer(jlong cookie); void onChoreographer() override { ChoreographerThread::onChoreographer(); }; private: void scheduleNextFrameCallback() override REQUIRES(mWaitingMutex); JavaVM *mJVM; JNIEnv *mEnv = nullptr; jobject mJobj = nullptr; jmethodID mJpostFrameCallback = nullptr; jmethodID mJterminate = nullptr; }; JavaChoreographerThread::JavaChoreographerThread(JavaVM *vm, Callback onChoreographer) : ChoreographerThread(onChoreographer), mJVM(vm) { JNIEnv *env; mJVM->AttachCurrentThread(&env, nullptr); jclass choreographerCallbackClass = env->FindClass("com/google/swappy/ChoreographerCallback"); jmethodID constructor = env->GetMethodID( choreographerCallbackClass, "", "(J)V"); mJpostFrameCallback = env->GetMethodID( choreographerCallbackClass, "postFrameCallback", "()V"); mJterminate = env->GetMethodID( choreographerCallbackClass, "terminate", "()V"); jobject choreographerCallback = env->NewObject(choreographerCallbackClass, constructor, reinterpret_cast(this)); mJobj = env->NewGlobalRef(choreographerCallback); } JavaChoreographerThread::~JavaChoreographerThread() { ALOGI("Destroying JavaChoreographerThread"); if (!mJobj) { return; } JNIEnv *env; mJVM->AttachCurrentThread(&env, nullptr); env->CallVoidMethod(mJobj, mJterminate); env->DeleteGlobalRef(mJobj); mJVM->DetachCurrentThread(); } void JavaChoreographerThread::scheduleNextFrameCallback() { JNIEnv *env; mJVM->AttachCurrentThread(&env, nullptr); env->CallVoidMethod(mJobj, mJpostFrameCallback); } void JavaChoreographerThread::onChoreographer(jlong cookie) { JavaChoreographerThread *me = reinterpret_cast(cookie); me->onChoreographer(); } extern "C" { JNIEXPORT void JNICALL Java_com_google_swappy_ChoreographerCallback_nOnChoreographer(JNIEnv * /* env */, jobject /* this */, jlong cookie, jlong frameTimeNanos) { JavaChoreographerThread::onChoreographer(cookie); } } // extern "C" class NoChoreographerThread : public ChoreographerThread { public: NoChoreographerThread(Callback onChoreographer); private: void postFrameCallbacks() override; void scheduleNextFrameCallback() override REQUIRES(mWaitingMutex); }; NoChoreographerThread::NoChoreographerThread(Callback onChoreographer) : ChoreographerThread(onChoreographer) {} void NoChoreographerThread::postFrameCallbacks() { mCallback(); } void NoChoreographerThread::scheduleNextFrameCallback() {} ChoreographerThread::ChoreographerThread(Callback onChoreographer): mCallback(onChoreographer) {} ChoreographerThread::~ChoreographerThread() = default; void ChoreographerThread::postFrameCallbacks() { TRACE_CALL(); // This method is called before calling to swap buffers // It registers to get MAX_CALLBACKS_BEFORE_IDLE frame callbacks before going idle // so if app goes to idle the thread will not get further frame callbacks std::lock_guard lock(mWaitingMutex); if (mCallbacksBeforeIdle == 0) { scheduleNextFrameCallback(); } mCallbacksBeforeIdle = MAX_CALLBACKS_BEFORE_IDLE; } void ChoreographerThread::onChoreographer() { TRACE_CALL(); { std::lock_guard lock(mWaitingMutex); mCallbacksBeforeIdle--; if (mCallbacksBeforeIdle > 0) { scheduleNextFrameCallback(); } } mCallback(); } int ChoreographerThread::getSDKVersion(JavaVM *vm) { JNIEnv *env; vm->AttachCurrentThread(&env, nullptr); const jclass buildClass = env->FindClass("android/os/Build$VERSION"); if (env->ExceptionCheck()) { env->ExceptionClear(); ALOGE("Failed to get Build.VERSION class"); return 0; } const jfieldID sdk_int = env->GetStaticFieldID(buildClass, "SDK_INT", "I"); if (env->ExceptionCheck()) { env->ExceptionClear(); ALOGE("Failed to get Build.VERSION.SDK_INT field"); return 0; } const jint sdk = env->GetStaticIntField(buildClass, sdk_int); if (env->ExceptionCheck()) { env->ExceptionClear(); ALOGE("Failed to get SDK version"); return 0; } ALOGI("SDK version = %d", sdk); return sdk; } bool ChoreographerThread::isChoreographerCallbackClassLoaded(JavaVM *vm) { JNIEnv *env; vm->AttachCurrentThread(&env, nullptr); env->FindClass("com/google/swappy/ChoreographerCallback"); if (env->ExceptionCheck()) { env->ExceptionClear(); return false; } return true; } std::unique_ptr ChoreographerThread::createChoreographerThread( Type type, JavaVM *vm, Callback onChoreographer) { if (type == Type::App) { ALOGI("Using Application's Choreographer"); return std::make_unique(onChoreographer); } if (getSDKVersion(vm) >= 24) { ALOGI("Using NDK Choreographer"); return std::make_unique(onChoreographer); } if (isChoreographerCallbackClassLoaded(vm)){ ALOGI("Using Java Choreographer"); return std::make_unique(vm, onChoreographer); } ALOGI("Using no Choreographer (Best Effort)"); return std::make_unique(onChoreographer); } } // namespace swappy