Android에서 VSync 동작 원리 및 초기화 과정(5)
이전 포스팅을 통해 안드로이드의 VSync( Vertical Synchronization, 수직동기화)가 초기화 되어가는 과정을 차근차근 살펴보았습니다. 이번 시간에는 C++ 단계에서 Java Framework 단계로 VSync가 동작하는 과정을 살펴보도록 하겠습니다. 본 포스팅의 이해를 위해 이전에 작성해 왔던 포스팅들을 참조해 주시길 바랍니다.
Android에서 VSync 동작 원리 및 초기화 과정(3)
SurfaceFlinger에서 DispSyncSource의 초기화 과정`
/frameworks/native/services/surfaceflinger/DispSync.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 | class DispSyncThread: public Thread { .... void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) { for (size_t i = 0; i < callbacks.size(); i++) { callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime); } } .... }; | cs |
DispSyncSource를 통해 설정하였던 Callback 함수인 onDispSyncEvent()를 호출합니다.
/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class DispSyncSource : public VSyncSource, private DispSync::Callback { public: DispSyncSource(DispSync* dispSync, nsecs_t phaseOffset, bool traceVsync) : mValue(0), mPhaseOffset(phaseOffset), mTraceVsync(traceVsync), mDispSync(dispSync) {} virtual ~DispSyncSource() {} .... virtual void setCallback(const sp<VSyncSource::Callback>& callback) { Mutex::Autolock lock(mMutex); mCallback = callback; } private: virtual void onDispSyncEvent(nsecs_t when) { sp<VSyncSource::Callback> callback; { Mutex::Autolock lock(mMutex); callback = mCallback; if (mTraceVsync) { mValue = (mValue + 1) % 2; ATRACE_INT("VSYNC", mValue); } } if (callback != NULL) { callback->onVSyncEvent(when); } } .... DispSync* mDispSync; sp<VSyncSource::Callback> mCallback; Mutex mMutex; }; | cs |
등록된 Callback 함수 onVSyncEvent()를 통해 EventThread 가 실행됩니다.
/frameworks/native/services/surfaceflinger/EventThread.cpp
1 2 3 4 5 6 7 8 | void EventThread::onVSyncEvent(nsecs_t timestamp) { Mutex::Autolock _l(mLock); mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC; mVSyncEvent[0].header.id = 0; mVSyncEvent[0].header.timestamp = timestamp; mVSyncEvent[0].vsync.count++; mCondition.broadcast(); } | cs |
EventThread의 Condition 클래스를 통해 broadcast()가 호출되어 wait() 상태에 있던 동작이 다시 재개됩니다.
/frameworks/native/services/surfaceflinger/EventThread.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | // This will return when (1) a vsync event has been received, and (2) there was // at least one connection interested in receiving it when we started waiting. Vector< sp<EventThread::Connection> > EventThread::waitForEvent( DisplayEventReceiver::Event* event) { Mutex::Autolock _l(mLock); Vector< sp<EventThread::Connection> > signalConnections; do { .... // find out connections waiting for events size_t count = mDisplayEventConnections.size(); for (size_t i=0 ; i<count ; i++) { sp<Connection> connection(mDisplayEventConnections[i].promote()); if (connection != NULL) { bool added = false; if (connection->count >= 0) { // we need vsync events because at least // one connection is waiting for it waitForVSync = true; if (timestamp) { // we consume the event only if it's time // (ie: we received a vsync event) if (connection->count == 0) { // fired this time around connection->count = -1; signalConnections.add(connection); added = true; } else if (connection->count == 1 || (vsyncCount % connection->count) == 0) { // continuous event, and time to report it signalConnections.add(connection); added = true; } } } if (eventPending && !timestamp && !added) { // we don't have a vsync event to process // (timestamp==0), but we have some pending // messages. signalConnections.add(connection); } } else { // we couldn't promote this reference, the connection has // died, so clean-up! mDisplayEventConnections.removeAt(i); --i; --count; } } .... // note: !timestamp implies signalConnections.isEmpty(), because we // don't populate signalConnections if there's no vsync pending if (!timestamp && !eventPending) { // wait for something to happen if (waitForVSync) { .... } else { // Nobody is interested in vsync, so we just want to sleep. // h/w vsync should be disabled, so this will wait until we // get a new connection, or an existing connection becomes // interested in receiving vsync again. mCondition.wait(mLock); } } } while (signalConnections.isEmpty()); // here we're guaranteed to have a timestamp and some connections to signal // (The connections might have dropped out of mDisplayEventConnections // while we were asleep, but we'll still have strong references to them.) return signalConnections; } | cs |
wait 상태에서 멈추어있던 waitForEvent() 함수가 broadcast 함수의 신호를 받고 다시 동작하며 waitForEvent() 함수의 실행을 종료시킵니다.
/frameworks/native/services/surfaceflinger/EventThread.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | bool EventThread::threadLoop() { DisplayEventReceiver::Event event; Vector< sp<EventThread::Connection> > signalConnections; signalConnections = waitForEvent(&event); // dispatch events to listeners... const size_t count = signalConnections.size(); for (size_t i=0 ; i<count ; i++) { const sp<Connection>& conn(signalConnections[i]); // now see if we still need to report this event status_t err = conn->postEvent(event); .... } return true; } | cs |
waitForEvent() 함수가 동작되는 동안 수신된 이벤트에 대해 postEvent() 함수를 호출합니다.
/frameworks/native/services/surfaceflinger/EventThread.cpp
1 2 3 4 5 | status_t EventThread::Connection::postEvent( const DisplayEventReceiver::Event& event) { ssize_t size = DisplayEventReceiver::sendEvents(mChannel, &event, 1); return size < 0 ? status_t(size) : status_t(NO_ERROR); } | cs |
DisplayEventReceiver 클래스 내에 있는 sendEvent() 함수를 호출합니다.
/frameworks/native/libs/gui/DisplayEventReceiver.cpp
1 2 3 4 5 | ssize_t DisplayEventReceiver::sendEvents(const sp<BitTube>& dataChannel, Event const* events, size_t count) { return BitTube::sendObjects(dataChannel, events, count); } | cs |
sendObjects() 함수를 통해 BitTube 클래스가 Pipe로 Looper와 연결한 File Descriptor에 값을 입력합니다.
/frameworks/native/libs/gui/DisplayEventReceiver.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ssize_t BitTube::sendObjects(const sp<BitTube>& tube, void const* events, size_t count, size_t objSize) { const char* vaddr = reinterpret_cast<const char*>(events); ssize_t size = tube->write(vaddr, count*objSize); // should never happen because of SOCK_SEQPACKET LOG_ALWAYS_FATAL_IF((size >= 0) && (size % objSize), "BitTube::sendObjects(count=%d, size=%d), res=%d (partial events were sent!)", count, objSize, size); //ALOGE_IF(size<0, "error %d sending %d events", size, count); return size < 0 ? size : size / objSize; } | cs |
write() 함수를 통해 Looper에 값을 입력합니다.
/frameworks/native/libs/gui/DisplayEventReceiver.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | void BitTube::init(size_t rcvbuf, size_t sndbuf) { int sockets[2]; if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) == 0) { size_t size = DEFAULT_SOCKET_BUFFER_SIZE; setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); // sine we don't use the "return channel", we keep it small... setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); fcntl(sockets[0], F_SETFL, O_NONBLOCK); fcntl(sockets[1], F_SETFL, O_NONBLOCK); mReceiveFd = sockets[0]; mSendFd = sockets[1]; } else { mReceiveFd = -errno; ALOGE("BitTube: pipe creation failed (%s)", strerror(-mReceiveFd)); } } .... ssize_t BitTube::write(void const* vaddr, size_t size) { ssize_t err, len; do { len = ::send(mSendFd, vaddr, size, MSG_DONTWAIT | MSG_NOSIGNAL); // cannot return less than size, since we're using SOCK_SEQPACKET err = len < 0 ? errno : 0; } while (err == EINTR); return err == 0 ? len : -err; } | cs |
File Descriptor를 통해 데이터를 전송합니다. 이는 이후 Looper에서 감지하여 이벤트를 처리하게 됩니다. Looper에 DisplayEventReceiver에서 wirte를 시도하였을 때 이를 읽어들이는 File Descriptor는 아래의 방법으로 등록됩니다.
/frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | status_t NativeDisplayEventReceiver::initialize() { status_t result = mReceiver.initCheck(); if (result) { ALOGW("Failed to initialize display event receiver, status=%d", result); return result; } int rc = mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0, ALOOPER_EVENT_INPUT, this, NULL); if (rc < 0) { return UNKNOWN_ERROR; } return OK; } | cs |
/frameworks/native/libs/gui/DisplayEventReceiver.cpp
1 2 3 4 5 6 | int DisplayEventReceiver::getFd() const { if (mDataChannel == NULL) return NO_INIT; return mDataChannel->getFd(); } | cs |
/frameworks/native/libs/gui/BitTube.cpp
1 2 3 4 | int BitTube::getFd() const { return mReceiveFd; } | cs |
위의 과정을 통해 Looper에 File Descriptor가 등록되며 이는 Looper 내에서 epoll_wait()함수에 의해 감지되어 이벤트를 등록합니다.
/system/core/libutils/Looper.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | int Looper::pollInner(int timeoutMillis) { .... int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); .... for (int i = 0; i < eventCount; i++) { int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; if (fd == mWakeReadPipeFd) { if (epollEvents & EPOLLIN) { awoken(); } else { ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents); } } else { ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex >= 0) { int events = 0; if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT; if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT; if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR; if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP; pushResponse(events, mRequests.valueAt(requestIndex)); } else { ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is " "no longer registered.", epollEvents, fd); } } } .... } | cs |
epoll_wait()에 의해 감지된 File Descriptor는 이후 등록된 이벤트와 함께 pushResponse() 함수를 통해 등록됩니다.
/system/core/libutils/Looper.cpp
1 2 3 4 5 6 | void Looper::pushResponse(int events, const Request& request) { Response response; response.events = events; response.request = request; mResponses.push(response); } | cs |
pushResponse() 함수에 의해 등록된 이벤트는 이후 가지고 있는 Callback 함수를 수행합니다.
/system/core/libutils/Looper.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | int Looper::pollInner(int timeoutMillis) { .... // Invoke all response callbacks. for (size_t i = 0; i < mResponses.size(); i++) { Response& response = mResponses.editItemAt(i); if (response.request.ident == ALOOPER_POLL_CALLBACK) { int fd = response.request.fd; int events = response.events; void* data = response.request.data; #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p", this, response.request.callback.get(), fd, events, data); #endif int callbackResult = response.request.callback->handleEvent(fd, events, data); if (callbackResult == 0) { removeFd(fd); } // Clear the callback reference in the response structure promptly because we // will not clear the response vector itself until the next poll. response.request.callback.clear(); result = ALOOPER_POLL_CALLBACK; } } return result; } | cs |
DisplayEventReceiver 클래스를 통해 등록하였던 Callback 함수인 handleEvent() 함수를 실행합니다.
/frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | int NativeDisplayEventReceiver::handleEvent(int receiveFd, int events, void* data) { if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { ALOGE("Display event receiver pipe was closed or an error occurred. " "events=0x%x", events); return 0; // remove the callback } if (!(events & ALOOPER_EVENT_INPUT)) { ALOGW("Received spurious callback for unhandled poll event. " "events=0x%x", events); return 1; // keep the callback } // Drain all pending events, keep the last vsync. nsecs_t vsyncTimestamp; int32_t vsyncDisplayId; uint32_t vsyncCount; if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) { ALOGV("receiver %p ~ Vsync pulse: timestamp=%lld, id=%d, count=%d", this, vsyncTimestamp, vsyncDisplayId, vsyncCount); mWaitingForVsync = false; dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount); } return 1; // keep the callback } | cs |
Callback 함수로서 handleEvent()함수가 실행되며 이는 Java Framework에 있는 함수를 호출합니다.
/frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) { JNIEnv* env = AndroidRuntime::getJNIEnv(); ALOGV("receiver %p ~ Invoking vsync handler.", this); env->CallVoidMethod(mReceiverObjGlobal, gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, id, count); ALOGV("receiver %p ~ Returned from vsync handler.", this); mMessageQueue->raiseAndClearException(env, "dispatchVsync"); } .... int register_android_view_DisplayEventReceiver(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/view/DisplayEventReceiver", gMethods, NELEM(gMethods)); LOG_FATAL_IF(res < 0, "Unable to register native methods."); FIND_CLASS(gDisplayEventReceiverClassInfo.clazz, "android/view/DisplayEventReceiver"); GET_METHOD_ID(gDisplayEventReceiverClassInfo.dispatchVsync, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JII)V"); GET_METHOD_ID(gDisplayEventReceiverClassInfo.dispatchHotplug, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JIZ)V"); return 0; } | cs |
/frameworks/base/core/java/android/view/DisplayEventReceiver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * Called when a vertical sync pulse is received. * The recipient should render a frame and then call {@link #scheduleVsync} * to schedule the next vertical sync pulse. * * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()} * timebase. * @param builtInDisplayId The surface flinger built-in display id such as * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}. * @param frame The frame number. Increases by one for each vertical sync interval. */ public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { } .... // Called from native code. @SuppressWarnings("unused") private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); } | cs |