Android에서 VSync 동작 원리 및 초기화 과정(4)

 이번 포스팅에서는 Java Framework 단계에서 초기화 되는 과정에서 VSync 관련 동작이 Native 단계와 연결되는 과정을 살펴보도록 하겠습니다.


 Java 단계에서 안드로이드 기반 디바이스의 화면을 관리하는 클래스로 Choreographer가 있습니다. 이 클래스는 화면의 변화나 입력등을 통해 그려지는 타이밍을 관리합니다. Choreographer는 디스플레이의 부속시스템으로부터 전달되는 VSync(Vertical Synchronization)와 같은 신호를 수신하고, 다음 디스플레이 프레임을 렌더링 하기 위해 작업을 스케줄링 합니다.


 그렇다면 이제 Choreographer가 초기화 되는 과정을 살펴보도록 하겠습니다.


/frameworks/base/core/java/android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            return new Choreographer(looper);
        }
    };
cs

 ThreadLocal을 통하여 Choreographer 값을 thread에 저장합니다. static으로 선언되어 있으므로 sThreadInstance의 값이 초기화 되면서 return 값인 new Choreographer()가 실행됩니다.


/frameworks/base/core/java/android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    // Enable/disable vsync for animations and drawing.
    private static final boolean USE_VSYNC = SystemProperties.getBoolean(
            "debug.choreographer.vsync"true);
 
....
 
    private Choreographer(Looper looper) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;
 
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
 
        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
    }
cs

 Choreographer의 Constructor가 생성되고 있습니다. VSync를 사용하고 있으므로 FrameDisplayEventReceiver()의 Constructor가 새로 생성됨을 확인하실 수 있습니다.


/frameworks/base/core/java/android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
 
        public FrameDisplayEventReceiver(Looper looper) {
            super(looper);
        }
 
....
 
    }
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
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * Provides a low-level mechanism for an application to receive display events
 * such as vertical sync.
 *
 * The display event receive is NOT thread safe.  Moreover, its methods must only
 * be called on the Looper thread to which it is attached.
 *
 * @hide
 */
public abstract class DisplayEventReceiver {
    private static final String TAG = "DisplayEventReceiver";
 
    private final CloseGuard mCloseGuard = CloseGuard.get();
 
    private int mReceiverPtr;
 
    // We keep a reference message queue object here so that it is not
    // GC'd while the native peer of the receiver is using them.
    private MessageQueue mMessageQueue;
 
    private static native int nativeInit(DisplayEventReceiver receiver,
            MessageQueue messageQueue);
    private static native void nativeDispose(int receiverPtr);
    private static native void nativeScheduleVsync(int receiverPtr);
 
    /**
     * Creates a display event receiver.
     *
     * @param looper The looper to use when invoking callbacks.
     */
    public DisplayEventReceiver(Looper looper) {
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }
 
        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(this, mMessageQueue);
 
        mCloseGuard.open("dispose");
    }
 
....
 
}
cs

 위 소스코드를 통해 DisplayEventReceiver 클래스에서 nativeInit를 통해 JNI로 연결되는 것을 보실 수 있습니다.


/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
static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj,
        jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }
 
    sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,
            receiverObj, messageQueue);
    status_t status = receiver->initialize();
    if (status) {
        String8 message;
        message.appendFormat("Failed to initialize display event receiver.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return 0;
    }
 
    receiver->incStrong(gDisplayEventReceiverClassInfo.clazz); // retain a reference for the object
    return reinterpret_cast<jint>(receiver.get());
}
cs

 NativeDisplayEventReceiver클래스의 Constructor가 생성된 후 initialize() 함수가 호출되고 있는 모습입니다.


/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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class NativeDisplayEventReceiver : public LooperCallback {
public:
    NativeDisplayEventReceiver(JNIEnv* env,
            jobject receiverObj, const sp<MessageQueue>& messageQueue);
 
    status_t initialize();
    void dispose();
    status_t scheduleVsync();
 
protected:
    virtual ~NativeDisplayEventReceiver();
 
private:
    jobject mReceiverObjGlobal;
    sp<MessageQueue> mMessageQueue;
    DisplayEventReceiver mReceiver;
    bool mWaitingForVsync;
 
    virtual int handleEvent(int receiveFd, int events, void* data);
    bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount);
    void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
    void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
};
 
 
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,
        jobject receiverObj, const sp<MessageQueue>& messageQueue) :
        mReceiverObjGlobal(env->NewGlobalRef(receiverObj)),
        mMessageQueue(messageQueue), mWaitingForVsync(false) {
    ALOGV("receiver %p ~ Initializing input event receiver."this);
}
 
NativeDisplayEventReceiver::~NativeDisplayEventReceiver() {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    env->DeleteGlobalRef(mReceiverObjGlobal);
}
 
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


 위의 과정을 보았을 때 MessageQueue 내에 들어있는 Looper를 얻어낸 후 addFd() 함수를 통해 DisplayEventReceiver의 File Description이 등록됩니다. NativeDisplayEventReceiver 내에 있는 mReceiver는 DisplayEventReceiver의 클래스 변스로 해당 클래스의 소스코드를 살펴보도록 하겠습니다.


/frameworks/native/include/gui/DisplayEventReceiver.h

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class DisplayEventReceiver {
public:
    enum {
        DISPLAY_EVENT_VSYNC = 'vsyn',
        DISPLAY_EVENT_HOTPLUG = 'plug'
    };
 
    struct Event {
 
        struct Header {
            uint32_t type;
            uint32_t id;
            nsecs_t timestamp;
        };
 
        struct VSync {
            uint32_t count;
        };
 
        struct Hotplug {
            bool connected;
        };
 
        Header header;
        union {
            VSync vsync;
            Hotplug hotplug;
        };
    };
 
public:
    /*
     * DisplayEventReceiver creates and registers an event connection with
     * SurfaceFlinger. VSync events are disabled by default. Call setVSyncRate
     * or requestNextVsync to receive them.
     * Other events start being delivered immediately.
     */
    DisplayEventReceiver();
 
    /*
     * ~DisplayEventReceiver severs the connection with SurfaceFlinger, new events
     * stop being delivered immediately. Note that the queue could have
     * some events pending. These will be delivered.
     */
    ~DisplayEventReceiver();
 
    /*
     * initCheck returns the state of DisplayEventReceiver after construction.
     */
    status_t initCheck() const;
 
    /*
     * getFd returns the file descriptor to use to receive events.
     * OWNERSHIP IS RETAINED by DisplayEventReceiver. DO NOT CLOSE this
     * file-descriptor.
     */
    int getFd() const;
 
    /*
     * getEvents reads events from the queue and returns how many events were
     * read. Returns 0 if there are no more events or a negative error code.
     * If NOT_ENOUGH_DATA is returned, the object has become invalid forever, it
     * should be destroyed and getEvents() shouldn't be called again.
     */
    ssize_t getEvents(Event* events, size_t count);
    static ssize_t getEvents(const sp<BitTube>& dataChannel,
            Event* events, size_t count);
 
    /*
     * sendEvents write events to the queue and returns how many events were
     * written.
     */
    static ssize_t sendEvents(const sp<BitTube>& dataChannel,
            Event const* events, size_t count);
 
    /*
     * setVsyncRate() sets the Event::VSync delivery rate. A value of
     * 1 returns every Event::VSync. A value of 2 returns every other event,
     * etc... a value of 0 returns no event unless  requestNextVsync() has
     * been called.
     */
    status_t setVsyncRate(uint32_t count);
 
    /*
     * requestNextVsync() schedules the next Event::VSync. It has no effect
     * if the vsync rate is > 0.
     */
    status_t requestNextVsync();
 
private:
    sp<IDisplayEventConnection> mEventConnection;
    sp<BitTube> mDataChannel;
};
cs


/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
DisplayEventReceiver::DisplayEventReceiver() {
    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
    if (sf != NULL) {
        mEventConnection = sf->createDisplayEventConnection();
        if (mEventConnection != NULL) {
            mDataChannel = mEventConnection->getDataChannel();
        }
    }
}
 
DisplayEventReceiver::~DisplayEventReceiver() {
}
 
status_t DisplayEventReceiver::initCheck() const {
    if (mDataChannel != NULL)
        return NO_ERROR;
    return NO_INIT;
}
 
int DisplayEventReceiver::getFd() const {
    if (mDataChannel == NULL)
        return NO_INIT;
 
    return mDataChannel->getFd();
}
cs


 DisplayEventReceiver의 소스코드를 하나씩 살펴보도록 하겠습니다.


sp<ISurfaceComposer> sf(ComposerService::getComposerService());

 SurfaceFlinger로부터 ComposerService의 Binder Proxy를 얻어옵니다.


/frameworks/native/libs/gui/SurfaceComposerClient.cpp

1
2
3
4
5
6
7
8
9
10
/*static*/ sp<ISurfaceComposer> ComposerService::getComposerService() {
    ComposerService& instance = ComposerService::getInstance();
    Mutex::Autolock _l(instance.mLock);
    if (instance.mComposerService == NULL) {
        ComposerService::getInstance().connectLocked();
        assert(instance.mComposerService != NULL);
        ALOGD("ComposerService reconnected");
    }
    return instance.mComposerService;
}
cs


/frameworks/native/include/private/gui/ComposerService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// This holds our connection to the composer service (i.e. SurfaceFlinger).
// If the remote side goes away, we will re-establish the connection.
// Users of this class should not retain the value from
// getComposerService() for an extended period.
//
// (It's not clear that using Singleton is useful here anymore.)
class ComposerService : public Singleton<ComposerService>
{
    sp<ISurfaceComposer> mComposerService;
    sp<IBinder::DeathRecipient> mDeathObserver;
    Mutex mLock;
 
    ComposerService();
    void connectLocked();
    void composerServiceDied();
    friend class Singleton<ComposerService>;
public:
 
    // Get a connection to the Composer Service.  This will block until
    // a connection is established.
    static sp<ISurfaceComposer> getComposerService();
};
cs

mEventConnection = sf->createDisplayEventConnection();

 SurfaceFlinger로 부터 디스플레이와 관련된 이벤트를 수신할 수 있는 EventConnection 클래스를 받습니다.


/frameworks/native/libs/gui/ISurfaceComposer.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
class BpSurfaceComposer : public BpInterface<ISurfaceComposer>
{
public:
    BpSurfaceComposer(const sp<IBinder>& impl)
        : BpInterface<ISurfaceComposer>(impl)
    {
    }
 
....
 
    virtual sp<IDisplayEventConnection> createDisplayEventConnection()
    {
        Parcel data, reply;
        sp<IDisplayEventConnection> result;
        int err = data.writeInterfaceToken(
                ISurfaceComposer::getInterfaceDescriptor());
        if (err != NO_ERROR) {
            return result;
        }
        err = remote()->transact(
                BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION,
                data, &reply);
        if (err != NO_ERROR) {
            ALOGE("ISurfaceComposer::createDisplayEventConnection: error performing "
                    "transaction: %s (%d)", strerror(-err), -err);
            return result;
        }
        result = interface_cast<IDisplayEventConnection>(reply.readStrongBinder());
        return result;
    }
 
....
 
}
 
status_t BnSurfaceComposer::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
 
....
 
        case CREATE_DISPLAY_EVENT_CONNECTION: {
            CHECK_INTERFACE(ISurfaceComposer, data, reply);
            sp<IDisplayEventConnection> connection(createDisplayEventConnection());
            reply->writeStrongBinder(connection->asBinder());
            return NO_ERROR;
        }
 
....
 
}
cs


/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
void SurfaceFlinger::init() {
 
....
 
    // start the EventThread
    sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            vsyncPhaseOffsetNs, true);
    mEventThread = new EventThread(vsyncSrc);
    sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            sfVsyncPhaseOffsetNs, false);
    mSFEventThread = new EventThread(sfVsyncSrc);
    mEventQueue.setEventThread(mSFEventThread);
 
    mEventControlThread = new EventControlThread(this);
    mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
 
....
 
}
 
....
 
sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection() {
    return mEventThread->createEventConnection();
}
cs

 EventThread를 통해 createEventConnection() 함수를 실행하게 됩니다. EventThread가 SurfaceFlinger에 적용되는 과정에 대해 알고 싶으신 분은 이전 포스팅의 내용을 참조해 주시기 바랍니다.


SurfaceFlinger에서 EventThread의 초기화 과정

http://elecs.tistory.com/140


/frameworks/native/services/surfaceflinger/EventThread.cpp

1
2
3
4
5
6
7
8
9
10
11
sp<EventThread::Connection> EventThread::createEventConnection() const {
    return new Connection(const_cast<EventThread*>(this));
}
 
....
 
EventThread::Connection::Connection(
        const sp<EventThread>& eventThread)
    : count(-1), mEventThread(eventThread), mChannel(new BitTube())
{
}
cs


mDataChannel = mEventConnection->getDataChannel();

 위 Connection 클래스를 생성하면서 만들어진 FD인 BitTube 클래스를 mDataChannel에 저장합니다.


/frameworks/native/services/surfaceflinger/EventThread.cpp

1
2
3
sp<BitTube> EventThread::Connection::getDataChannel() const {
    return mChannel;
}
cs


 이후 등록된 FD가 호출될 경우 VSync 관련 이벤트가 발생하게 됩니다.

/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
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
79
80
81
82
83
84
85
86
87
88
89
90
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p"this, fd, ident,
            events, callback.get(), data);
#endif
 
    if (!callback.get()) {
        if (! mAllowNonCallbacks) {
            ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
            return -1;
        }
 
        if (ident < 0) {
            ALOGE("Invalid attempt to set NULL callback with ident < 0.");
            return -1;
        }
    } else {
        ident = ALOOPER_POLL_CALLBACK;
    }
 
    int epollEvents = 0;
    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
 
    { // acquire lock
        AutoMutex _l(mLock);
 
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.callback = callback;
        request.data = data;
 
        struct epoll_event eventItem;
        memset(& eventItem, 0sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = epollEvents;
        eventItem.data.fd = fd;
 
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.add(fd, request);
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    } // release lock
    return 1;
}
 
....
 
int Looper::pollInner(int timeoutMillis) {
 
....
 
    // Release lock.
    mLock.unlock();
 
    // 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



  • 낙은헤 2016.02.02 17:42 ADDR 수정/삭제 답글

    와... 안드로이드 프레임웍을 이렇게 깊이있게 다룬 글은 처음 보네요
    좋은자료 정말 감사드립니다. 지금 수준으로는 이해가 어려운 부분이 많지만 틈틈이 찾아와서 공부해야 겠네요^^

    이 게시글과 직접적인 관련이 있는 것은 아니지만, 질문 하나 드려도 될까요?
    View 의 invalidate() 메소드를 호출해서 다시 그리기를 구현하고 있는데,
    사용자의 입력이 유지되면 invalidate() 가 연속적으로 호출되기 때문에,
    초당 60번 이상 호출이 되지 않도록 코드를 수정하고 있었습니다.
    그런데 지인의 이야기로는, 어차피 Choreographer 클래스가 VSync 신호에 맞춰서 화면을 갱신하기 때문에 그런 작업이
    불필요 하다고 하더라고요.
    하지만 invalidate() 내부에서 어떤 작업을 하는지 구체적으로 잘 모르기 때문에 찜찜하네요.
    화면 갱신을 요청하는 작업 하나뿐이라면야 여러번 호출하는 것이 크게 상관은 없겠지만,
    그외에 부가적인 작업이 동반된다면 한번의 VSync 주기 내에서 불필요한 오버헤드가 발생하게 될테니까요.

    저는 원래 어떤 프로그래밍을 하든간에 함수 호출도 오버헤드라는 생각을 하면서 가능한한 함수호출은 최소한으로
    하자는 철학(?)이 있어서 무엇이 옳은 것인지 확신이 서지 않습니다.
    사실 시간 간격을 확인하려고 해도 어차피 시간 확인을 위한 메소드를 호출해야하기 때문에 이번의 경우는 더더욱
    애매합니다.
    invalidate() 반복 호출에서 부하가 발생하지 않는다면, 코드 가독성 측면에서도 그냥 invalidate() 를 사용하는 것이
    맞을 것 같기도 합니다.
    어떻게 생각하세요???

  • Justin T. 2016.02.04 02:03 신고 ADDR 수정/삭제 답글

    안드로이드 기기의 화면과 수직동기화, 즉 VSync를 하는 이유는 하드웨어적인 측면이 강합니다.
    애초에 우리가 보는 스마트폰의 화면은 정지된 화면이더라도 초당 60회의 변화가 일어나고 있습니다. Vsync 또한 이러한 하드웨어의 특성에 맞게 설계된 것이고요.
    질문자께서 말씀하신대로 화면에 변화가 없는 상태에서 VSync가 발생하면 불필요한 오버헤드가 발생한다는 것은 사실입니다.
    그러나 VSync 신호 자체는 하드웨어 단계에서의 Interrupt가 발생하고 있는 것과 같은 상황이기 때문에 이를 소프트웨어 단계에서 제어하기에는 상당한 어려움이 있습니다. 설령 되는 방법을 찾았다 하더라도 가장 쉬운 방법은 하드웨어를 직접 다시 디자인하는 방법이라 생각됩니다 ㅎㅎ
    수직동기화를 해주어야 하는 이유에 대해 자세히 찾아보시면 답은 나올것이라 생각됩니다.

  • 낙은헤 2016.02.05 21:40 ADDR 수정/삭제 답글

    답변 감사드립니다~ 좋은 하루 되세요:)

  • 2016.09.22 22:46 ADDR 수정/삭제 답글

    안녕하세요. 몇달전 글이지만, 혹시 보실까 싶어 글 남깁니다.
    저는 app정도만 개발해보았는데요. 최근에 성능테스트 때문에 vsync 를 끄고 싶은데, 방법을 찾다가 이곳까지 왔습니다.
    처음엔 openGL 함수로 제어가능할거라 생각했었는데, 그게 아닌것 같아서요.
    윗글에서 말씀하신것처럼 하드웨어문제로 볼수 있어서, 안드로이드 함수를 통해 꺼야할것 같은데, 가능할까요?
    혹시 방법을 아시거나 reference할만한 곳 알려주시면 감사하겠습니다.

    조금 더 말씀드리자면, 최대 frame rate(fps)를 계산하고 싶은 상황입니다. opengl의 rendering mode를 RENDERMODE_CONTINUOUSLY 로 설정해도 자체적으로 60fps를 지키는 것같습니다. 아주 심플한 rendering을 해도 60fps보다 높아지지 않더라고요. 제생각에는 vsync때문에 더 많이 자주 그릴 수 있어도 딱 60번만 하는 것같습니다.
    그래서 vsync를 끄고 최대한 얼마나 자주 그리나 알아보고 싶은 상황입니다.
    잘 전달이 됐나 모르겠네요.
    그럼 좋은하루 되세요.

    • Justin T. 2016.09.23 00:49 신고 수정/삭제

      반갑습니다.
      단도직입적으로 안드로이드의 vsync 기능은 운영체제 내 프레임워크 단계를 수정해야 하기 때문에 대단히 어려운 작업입니다. 이를 수정하려면 관련된 소스코드의 동작과정을 모두 이해하셔야 겨우 수정이 가능하며 다른 알고리즘들과 엮여서 동작하기 때문에 vsync 기능만 따로 설정하는 것은 상당히 어려운 작업이 될 것이라는 것이 제 의견입니다.
      속시원한 답변을 드리지 못한 점 죄송합니다.