| /* |
| * 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: |
| explicit 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)); |
| } |