| /* | |
| * Copyright (C) 2015 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 "Choreographer" | |
| //#define LOG_NDEBUG 0 | |
| #include <cinttypes> | |
| #include <queue> | |
| #include <thread> | |
| #include <android/choreographer.h> | |
| #include <androidfw/DisplayEventDispatcher.h> | |
| #include <gui/ISurfaceComposer.h> | |
| #include <utils/Looper.h> | |
| #include <utils/Mutex.h> | |
| #include <utils/Timers.h> | |
| namespace android { | |
| static inline const char* toString(bool value) { | |
| return value ? "true" : "false"; | |
| } | |
| struct FrameCallback { | |
| AChoreographer_frameCallback callback; | |
| void* data; | |
| nsecs_t dueTime; | |
| inline bool operator<(const FrameCallback& rhs) const { | |
| // Note that this is intentionally flipped because we want callbacks due sooner to be at | |
| // the head of the queue | |
| return dueTime > rhs.dueTime; | |
| } | |
| }; | |
| class Choreographer : public DisplayEventDispatcher, public MessageHandler { | |
| public: | |
| void postFrameCallback(AChoreographer_frameCallback cb, void* data); | |
| void postFrameCallbackDelayed(AChoreographer_frameCallback cb, void* data, nsecs_t delay); | |
| enum { | |
| MSG_SCHEDULE_CALLBACKS = 0, | |
| MSG_SCHEDULE_VSYNC = 1 | |
| }; | |
| virtual void handleMessage(const Message& message) override; | |
| static Choreographer* getForThread(); | |
| protected: | |
| virtual ~Choreographer() = default; | |
| private: | |
| Choreographer(const sp<Looper>& looper); | |
| Choreographer(const Choreographer&) = delete; | |
| virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count); | |
| virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected); | |
| void scheduleCallbacks(); | |
| // Protected by mLock | |
| std::priority_queue<FrameCallback> mCallbacks; | |
| mutable Mutex mLock; | |
| const sp<Looper> mLooper; | |
| const std::thread::id mThreadId; | |
| }; | |
| static thread_local Choreographer* gChoreographer; | |
| Choreographer* Choreographer::getForThread() { | |
| if (gChoreographer == nullptr) { | |
| sp<Looper> looper = Looper::getForThread(); | |
| if (!looper.get()) { | |
| ALOGW("No looper prepared for thread"); | |
| return nullptr; | |
| } | |
| gChoreographer = new Choreographer(looper); | |
| status_t result = gChoreographer->initialize(); | |
| if (result != OK) { | |
| ALOGW("Failed to initialize"); | |
| return nullptr; | |
| } | |
| } | |
| return gChoreographer; | |
| } | |
| Choreographer::Choreographer(const sp<Looper>& looper) : | |
| DisplayEventDispatcher(looper), mLooper(looper), mThreadId(std::this_thread::get_id()) { | |
| } | |
| void Choreographer::postFrameCallback(AChoreographer_frameCallback cb, void* data) { | |
| postFrameCallbackDelayed(cb, data, 0); | |
| } | |
| void Choreographer::postFrameCallbackDelayed( | |
| AChoreographer_frameCallback cb, void* data, nsecs_t delay) { | |
| nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); | |
| FrameCallback callback{cb, data, now + delay}; | |
| { | |
| AutoMutex _l{mLock}; | |
| mCallbacks.push(callback); | |
| } | |
| if (callback.dueTime <= now) { | |
| if (std::this_thread::get_id() != mThreadId) { | |
| Message m{MSG_SCHEDULE_VSYNC}; | |
| mLooper->sendMessage(this, m); | |
| } else { | |
| scheduleVsync(); | |
| } | |
| } else { | |
| Message m{MSG_SCHEDULE_CALLBACKS}; | |
| mLooper->sendMessageDelayed(delay, this, m); | |
| } | |
| } | |
| void Choreographer::scheduleCallbacks() { | |
| AutoMutex _{mLock}; | |
| nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); | |
| if (mCallbacks.top().dueTime <= now) { | |
| ALOGV("choreographer %p ~ scheduling vsync", this); | |
| scheduleVsync(); | |
| return; | |
| } | |
| } | |
| void Choreographer::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t) { | |
| if (id != ISurfaceComposer::eDisplayIdMain) { | |
| ALOGV("choreographer %p ~ ignoring vsync signal for non-main display (id=%d)", this, id); | |
| scheduleVsync(); | |
| return; | |
| } | |
| std::vector<FrameCallback> callbacks{}; | |
| { | |
| AutoMutex _l{mLock}; | |
| nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); | |
| while (!mCallbacks.empty() && mCallbacks.top().dueTime < now) { | |
| callbacks.push_back(mCallbacks.top()); | |
| mCallbacks.pop(); | |
| } | |
| } | |
| for (const auto& cb : callbacks) { | |
| cb.callback(timestamp, cb.data); | |
| } | |
| } | |
| void Choreographer::dispatchHotplug(nsecs_t, int32_t id, bool connected) { | |
| ALOGV("choreographer %p ~ received hotplug event (id=%" PRId32 ", connected=%s), ignoring.", | |
| this, id, toString(connected)); | |
| } | |
| void Choreographer::handleMessage(const Message& message) { | |
| switch (message.what) { | |
| case MSG_SCHEDULE_CALLBACKS: | |
| scheduleCallbacks(); | |
| break; | |
| case MSG_SCHEDULE_VSYNC: | |
| scheduleVsync(); | |
| break; | |
| } | |
| } | |
| } | |
| /* Glue for the NDK interface */ | |
| using android::Choreographer; | |
| static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) { | |
| return reinterpret_cast<Choreographer*>(choreographer); | |
| } | |
| static inline AChoreographer* Choreographer_to_AChoreographer(Choreographer* choreographer) { | |
| return reinterpret_cast<AChoreographer*>(choreographer); | |
| } | |
| AChoreographer* AChoreographer_getInstance() { | |
| return Choreographer_to_AChoreographer(Choreographer::getForThread()); | |
| } | |
| void AChoreographer_postFrameCallback(AChoreographer* choreographer, | |
| AChoreographer_frameCallback callback, void* data) { | |
| AChoreographer_to_Choreographer(choreographer)->postFrameCallback(callback, data); | |
| } | |
| void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer, | |
| AChoreographer_frameCallback callback, void* data, long delayMillis) { | |
| AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed( | |
| callback, data, ms2ns(delayMillis)); | |
| } |