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