• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "ChoreographerThread"
18 
19 #include <android/looper.h>
20 #include <jni.h>
21 
22 #include "ChoreographerThread.h"
23 #include "Thread.h"
24 #include "CpuInfo.h"
25 
26 #include <condition_variable>
27 #include <cstring>
28 #include <cstdlib>
29 
30 #include <sched.h>
31 #include <pthread.h>
32 #include <unistd.h>
33 
34 #include "ChoreographerShim.h"
35 #include "Log.h"
36 #include "Trace.h"
37 
38 namespace swappy {
39 
40 // AChoreographer is supported from API 24. To allow compilation for minSDK < 24
41 // and still use AChoreographer for SDK >= 24 we need runtime support to call
42 // AChoreographer APIs.
43 
44 using PFN_AChoreographer_getInstance = AChoreographer* (*)();
45 
46 using PFN_AChoreographer_postFrameCallback = void (*)(AChoreographer* choreographer,
47                                                   AChoreographer_frameCallback callback,
48                                                   void* data);
49 
50 using PFN_AChoreographer_postFrameCallbackDelayed = void (*)(AChoreographer* choreographer,
51                                                         AChoreographer_frameCallback callback,
52                                                         void* data,
53                                                         long delayMillis);
54 
55 class NDKChoreographerThread : public ChoreographerThread {
56 public:
57     NDKChoreographerThread(Callback onChoreographer);
58     ~NDKChoreographerThread() override;
59 
60 private:
61     void looperThread();
62     void scheduleNextFrameCallback() override REQUIRES(mWaitingMutex);
63 
64     PFN_AChoreographer_getInstance mAChoreographer_getInstance = nullptr;
65     PFN_AChoreographer_postFrameCallback mAChoreographer_postFrameCallback = nullptr;
66     PFN_AChoreographer_postFrameCallbackDelayed mAChoreographer_postFrameCallbackDelayed = nullptr;
67     void *mLibAndroid = nullptr;
68     std::thread mThread;
69     std::condition_variable mWaitingCondition;
70     ALooper *mLooper GUARDED_BY(mWaitingMutex) = nullptr;
71     bool mThreadRunning GUARDED_BY(mWaitingMutex) = false;
72     AChoreographer *mChoreographer GUARDED_BY(mWaitingMutex) = nullptr;
73 };
74 
NDKChoreographerThread(Callback onChoreographer)75 NDKChoreographerThread::NDKChoreographerThread(Callback onChoreographer) :
76     ChoreographerThread(onChoreographer)
77 {
78     mLibAndroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
79     if (mLibAndroid == nullptr) {
80         ALOGE("FATAL: cannot open libandroid.so: %s", strerror(errno));
81         abort();
82     }
83 
84     mAChoreographer_getInstance =
85             reinterpret_cast<PFN_AChoreographer_getInstance >(
86                 dlsym(mLibAndroid, "AChoreographer_getInstance"));
87 
88     mAChoreographer_postFrameCallback =
89             reinterpret_cast<PFN_AChoreographer_postFrameCallback >(
90                     dlsym(mLibAndroid, "AChoreographer_postFrameCallback"));
91 
92     mAChoreographer_postFrameCallbackDelayed =
93             reinterpret_cast<PFN_AChoreographer_postFrameCallbackDelayed >(
94                     dlsym(mLibAndroid, "AChoreographer_postFrameCallbackDelayed"));
95 
96     if (!mAChoreographer_getInstance ||
97         !mAChoreographer_postFrameCallback ||
98         !mAChoreographer_postFrameCallbackDelayed) {
99         ALOGE("FATAL: cannot get AChoreographer symbols");
100         abort();
101     }
102 
103     std::unique_lock<std::mutex> lock(mWaitingMutex);
104     // create a new ALooper thread to get Choreographer events
105     mThreadRunning = true;
106     mThread = std::thread([this]() { looperThread(); });
107     mWaitingCondition.wait(lock, [&]() REQUIRES(mWaitingMutex) {
108         return mChoreographer != nullptr;
109     });
110 }
111 
~NDKChoreographerThread()112 NDKChoreographerThread::~NDKChoreographerThread()
113 {
114     ALOGI("Destroying NDKChoreographerThread");
115 
116     if (mLibAndroid != nullptr)
117       dlclose(mLibAndroid);
118 
119     if (!mLooper) {
120         return;
121     }
122 
123     ALooper_acquire(mLooper);
124     mThreadRunning = false;
125     ALooper_wake(mLooper);
126     ALooper_release(mLooper);
127     mThread.join();
128 }
129 
looperThread()130 void NDKChoreographerThread::looperThread()
131 {
132     int outFd, outEvents;
133     void *outData;
134     std::lock_guard<std::mutex> lock(mWaitingMutex);
135 
136     mLooper = ALooper_prepare(0);
137     if (!mLooper) {
138         ALOGE("ALooper_prepare failed");
139         return;
140     }
141 
142     mChoreographer = mAChoreographer_getInstance();
143     if (!mChoreographer) {
144         ALOGE("AChoreographer_getInstance failed");
145         return;
146     }
147 
148     mWaitingCondition.notify_all();
149 
150     const char *name = "SwappyChoreographer";
151 
152     CpuInfo cpu;
153     cpu_set_t cpu_set;
154     CPU_ZERO(&cpu_set);
155     CPU_SET(0, &cpu_set);
156 
157     if (cpu.getNumberOfCpus() > 0) {
158         ALOGI("Swappy found %d CPUs [%s].", cpu.getNumberOfCpus(), cpu.getHardware().c_str());
159         if (cpu.getNumberOfLittleCores() > 0) {
160             cpu_set = cpu.getLittleCoresMask();
161         }
162     }
163 
164     const auto tid = gettid();
165     ALOGI("Setting '%s' thread [%d-0x%x] affinity mask to 0x%x.",
166           name, tid, tid, to_mask(cpu_set));
167     sched_setaffinity(tid, sizeof(cpu_set), &cpu_set);
168 
169     pthread_setname_np(pthread_self(), name);
170 
171     while (mThreadRunning) {
172         // mutex should be unlocked before sleeping on pollAll
173         mWaitingMutex.unlock();
174         ALooper_pollAll(-1, &outFd, &outEvents, &outData);
175         mWaitingMutex.lock();
176     }
177     ALOGI("Terminating Looper thread");
178 
179     return;
180 }
181 
scheduleNextFrameCallback()182 void NDKChoreographerThread::scheduleNextFrameCallback()
183 {
184     AChoreographer_frameCallback frameCallback =
185             [](long frameTimeNanos, void *data) {
186                 reinterpret_cast<NDKChoreographerThread*>(data)->onChoreographer();
187             };
188 
189     mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
190 }
191 
192 class JavaChoreographerThread : public ChoreographerThread {
193 public:
194     JavaChoreographerThread(JavaVM *vm, Callback onChoreographer);
195     ~JavaChoreographerThread() override;
196     static void onChoreographer(jlong cookie);
onChoreographer()197     void onChoreographer() override { ChoreographerThread::onChoreographer(); };
198 
199 private:
200     void scheduleNextFrameCallback() override REQUIRES(mWaitingMutex);
201 
202     JavaVM *mJVM;
203     JNIEnv *mEnv = nullptr;
204     jobject mJobj = nullptr;
205     jmethodID mJpostFrameCallback = nullptr;
206     jmethodID mJterminate = nullptr;
207 
208 };
209 
JavaChoreographerThread(JavaVM * vm,Callback onChoreographer)210 JavaChoreographerThread::JavaChoreographerThread(JavaVM *vm,
211                                                  Callback onChoreographer) :
212         ChoreographerThread(onChoreographer),
213         mJVM(vm)
214 {
215 
216     JNIEnv *env;
217     mJVM->AttachCurrentThread(&env, nullptr);
218 
219     jclass choreographerCallbackClass = env->FindClass("com/google/swappy/ChoreographerCallback");
220 
221     jmethodID constructor = env->GetMethodID(
222             choreographerCallbackClass,
223             "<init>",
224             "(J)V");
225 
226     mJpostFrameCallback = env->GetMethodID(
227             choreographerCallbackClass,
228             "postFrameCallback",
229             "()V");
230 
231     mJterminate = env->GetMethodID(
232             choreographerCallbackClass,
233             "terminate",
234             "()V");
235 
236     jobject choreographerCallback = env->NewObject(choreographerCallbackClass, constructor, reinterpret_cast<jlong>(this));
237 
238     mJobj = env->NewGlobalRef(choreographerCallback);
239 }
240 
~JavaChoreographerThread()241 JavaChoreographerThread::~JavaChoreographerThread()
242 {
243     ALOGI("Destroying JavaChoreographerThread");
244 
245     if (!mJobj) {
246         return;
247     }
248 
249     JNIEnv *env;
250     mJVM->AttachCurrentThread(&env, nullptr);
251     env->CallVoidMethod(mJobj, mJterminate);
252     env->DeleteGlobalRef(mJobj);
253     mJVM->DetachCurrentThread();
254 }
255 
scheduleNextFrameCallback()256 void JavaChoreographerThread::scheduleNextFrameCallback()
257 {
258     JNIEnv *env;
259     mJVM->AttachCurrentThread(&env, nullptr);
260     env->CallVoidMethod(mJobj, mJpostFrameCallback);
261 }
262 
onChoreographer(jlong cookie)263 void JavaChoreographerThread::onChoreographer(jlong cookie) {
264     JavaChoreographerThread *me = reinterpret_cast<JavaChoreographerThread*>(cookie);
265     me->onChoreographer();
266 }
267 
268 extern "C" {
269 
270 JNIEXPORT void JNICALL
Java_com_google_swappy_ChoreographerCallback_nOnChoreographer(JNIEnv *,jobject,jlong cookie,jlong frameTimeNanos)271 Java_com_google_swappy_ChoreographerCallback_nOnChoreographer(JNIEnv * /* env */, jobject /* this */,
272                                                               jlong cookie, jlong frameTimeNanos) {
273     JavaChoreographerThread::onChoreographer(cookie);
274 }
275 
276 } // extern "C"
277 
278 class NoChoreographerThread : public ChoreographerThread {
279 public:
280     NoChoreographerThread(Callback onChoreographer);
281 
282 private:
283     void postFrameCallbacks() override;
284     void scheduleNextFrameCallback() override REQUIRES(mWaitingMutex);
285 };
286 
NoChoreographerThread(Callback onChoreographer)287 NoChoreographerThread::NoChoreographerThread(Callback onChoreographer) :
288     ChoreographerThread(onChoreographer) {}
289 
290 
postFrameCallbacks()291 void NoChoreographerThread::postFrameCallbacks() {
292     mCallback();
293 }
294 
scheduleNextFrameCallback()295 void NoChoreographerThread::scheduleNextFrameCallback() {}
296 
ChoreographerThread(Callback onChoreographer)297 ChoreographerThread::ChoreographerThread(Callback onChoreographer):
298         mCallback(onChoreographer) {}
299 
300 ChoreographerThread::~ChoreographerThread() = default;
301 
postFrameCallbacks()302 void ChoreographerThread::postFrameCallbacks()
303 {
304     TRACE_CALL();
305 
306     // This method is called before calling to swap buffers
307     // It registers to get MAX_CALLBACKS_BEFORE_IDLE frame callbacks before going idle
308     // so if app goes to idle the thread will not get further frame callbacks
309     std::lock_guard<std::mutex> lock(mWaitingMutex);
310     if (mCallbacksBeforeIdle == 0) {
311         scheduleNextFrameCallback();
312     }
313     mCallbacksBeforeIdle = MAX_CALLBACKS_BEFORE_IDLE;
314 }
315 
onChoreographer()316 void ChoreographerThread::onChoreographer()
317 {
318     TRACE_CALL();
319 
320     {
321         std::lock_guard<std::mutex> lock(mWaitingMutex);
322         mCallbacksBeforeIdle--;
323 
324         if (mCallbacksBeforeIdle > 0) {
325             scheduleNextFrameCallback();
326         }
327     }
328     mCallback();
329 }
330 
getSDKVersion(JavaVM * vm)331 int ChoreographerThread::getSDKVersion(JavaVM *vm)
332 {
333     JNIEnv *env;
334     vm->AttachCurrentThread(&env, nullptr);
335 
336     const jclass buildClass = env->FindClass("android/os/Build$VERSION");
337     if (env->ExceptionCheck()) {
338         env->ExceptionClear();
339         ALOGE("Failed to get Build.VERSION class");
340         return 0;
341     }
342 
343     const jfieldID sdk_int = env->GetStaticFieldID(buildClass, "SDK_INT", "I");
344     if (env->ExceptionCheck()) {
345         env->ExceptionClear();
346         ALOGE("Failed to get Build.VERSION.SDK_INT field");
347         return 0;
348     }
349 
350     const jint sdk = env->GetStaticIntField(buildClass, sdk_int);
351     if (env->ExceptionCheck()) {
352         env->ExceptionClear();
353         ALOGE("Failed to get SDK version");
354         return 0;
355     }
356 
357     ALOGI("SDK version = %d", sdk);
358     return sdk;
359 }
360 
isChoreographerCallbackClassLoaded(JavaVM * vm)361 bool ChoreographerThread::isChoreographerCallbackClassLoaded(JavaVM *vm)
362 {
363     JNIEnv *env;
364     vm->AttachCurrentThread(&env, nullptr);
365 
366     env->FindClass("com/google/swappy/ChoreographerCallback");
367     if (env->ExceptionCheck()) {
368         env->ExceptionClear();
369         return false;
370     }
371 
372     return true;
373 }
374 
375 std::unique_ptr<ChoreographerThread>
createChoreographerThread(Type type,JavaVM * vm,Callback onChoreographer)376     ChoreographerThread::createChoreographerThread(
377                 Type type, JavaVM *vm, Callback onChoreographer) {
378     if (type == Type::App) {
379         ALOGI("Using Application's Choreographer");
380         return std::make_unique<NoChoreographerThread>(onChoreographer);
381     }
382 
383     if (getSDKVersion(vm) >= 24) {
384         ALOGI("Using NDK Choreographer");
385         return std::make_unique<NDKChoreographerThread>(onChoreographer);
386     }
387 
388     if (isChoreographerCallbackClassLoaded(vm)){
389         ALOGI("Using Java Choreographer");
390         return std::make_unique<JavaChoreographerThread>(vm, onChoreographer);
391     }
392 
393     ALOGI("Using no Choreographer (Best Effort)");
394     return std::make_unique<NoChoreographerThread>(onChoreographer);
395 }
396 
397 } // namespace swappy
398