Add choreographer API to the NDK.
Change-Id: Icb8cffd3cd3bd06814466be72db3e26f6a62cbc6
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
new file mode 100644
index 0000000..cc2fd77
--- /dev/null
+++ b/native/android/choreographer.cpp
@@ -0,0 +1,205 @@
+/*
+ * 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;
+};
+
+
+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));
+}