OSDN Git Service

Add support for sending VSYNC events to the framework
authorMathias Agopian <mathias@google.com>
Fri, 18 Nov 2011 01:49:17 +0000 (17:49 -0800)
committerMathias Agopian <mathias@google.com>
Tue, 29 Nov 2011 21:10:25 +0000 (13:10 -0800)
use gui/DisplayEvent to receive the events. Events are
dispatched through a unix pipe, so the API is compatible
with utils/Looper. see gui/DisplayEvent.h for more info.

Bug: 1475048
Change-Id: Ia720f64d1b950328b47b22c6a86042e481d35f09

22 files changed:
include/gui/DisplayEventReceiver.h [new file with mode: 0644]
include/gui/IDisplayEventConnection.h [new file with mode: 0644]
include/surfaceflinger/ISurfaceComposer.h
libs/gui/Android.mk
libs/gui/DisplayEventReceiver.cpp [new file with mode: 0644]
libs/gui/IDisplayEventConnection.cpp [new file with mode: 0644]
libs/gui/ISurfaceComposer.cpp
services/surfaceflinger/Android.mk
services/surfaceflinger/DisplayEventConnection.cpp [new file with mode: 0644]
services/surfaceflinger/DisplayEventConnection.h [new file with mode: 0644]
services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
services/surfaceflinger/DisplayHardware/DisplayHardware.h
services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp [new file with mode: 0644]
services/surfaceflinger/DisplayHardware/VSyncBarrier.h [new file with mode: 0644]
services/surfaceflinger/EventThread.cpp [new file with mode: 0644]
services/surfaceflinger/EventThread.h [new file with mode: 0644]
services/surfaceflinger/SurfaceFlinger.cpp
services/surfaceflinger/SurfaceFlinger.h
services/surfaceflinger/tests/vsync/Android.mk [new file with mode: 0644]
services/surfaceflinger/tests/vsync/vsync.cpp [new file with mode: 0644]
services/surfaceflinger/tests/waitforvsync/Android.mk [new file with mode: 0644]
services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp [new file with mode: 0644]

diff --git a/include/gui/DisplayEventReceiver.h b/include/gui/DisplayEventReceiver.h
new file mode 100644 (file)
index 0000000..8d07c0e
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef ANDROID_GUI_DISPLAY_EVENT_H
+#define ANDROID_GUI_DISPLAY_EVENT_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+#include <binder/IInterface.h>
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class BitTube;
+class IDisplayEventConnection;
+
+// ----------------------------------------------------------------------------
+
+class DisplayEventReceiver {
+public:
+    enum {
+        DISPLAY_EVENT_VSYNC = 'vsyn'
+    };
+
+    struct Event {
+
+        struct Header {
+            uint32_t type;
+            nsecs_t timestamp;
+        };
+
+        struct VSync {
+            uint32_t count;
+        };
+
+        Header header;
+        union {
+            VSync vsync;
+        };
+    };
+
+public:
+    /*
+     * DisplayEventReceiver creates and registers an event connection with
+     * SurfaceFlinger. 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 event 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);
+
+private:
+    sp<IDisplayEventConnection> mEventConnection;
+    sp<BitTube> mDataChannel;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_DISPLAY_EVENT_H
diff --git a/include/gui/IDisplayEventConnection.h b/include/gui/IDisplayEventConnection.h
new file mode 100644 (file)
index 0000000..8728bb5
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef ANDROID_GUI_IDISPLAY_EVENT_CONNECTION_H
+#define ANDROID_GUI_IDISPLAY_EVENT_CONNECTION_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include <binder/IInterface.h>
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class BitTube;
+
+class IDisplayEventConnection : public IInterface
+{
+public:
+    DECLARE_META_INTERFACE(DisplayEventConnection);
+
+    virtual sp<BitTube> getDataChannel() const = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnDisplayEventConnection : public BnInterface<IDisplayEventConnection>
+{
+public:
+    virtual status_t    onTransact( uint32_t code,
+                                    const Parcel& data,
+                                    Parcel* reply,
+                                    uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_IDISPLAY_EVENT_CONNECTION_H
index 5eb09c7..58fd89d 100644 (file)
@@ -33,8 +33,9 @@
 namespace android {
 // ----------------------------------------------------------------------------
 
-class IMemoryHeap;
 class ComposerState;
+class IDisplayEventConnection;
+class IMemoryHeap;
 
 class ISurfaceComposer : public IInterface
 {
@@ -124,13 +125,19 @@ public:
             uint32_t reqWidth, uint32_t reqHeight,
             uint32_t minLayerZ, uint32_t maxLayerZ) = 0;
 
+    /* triggers screen off animation */
     virtual status_t turnElectronBeamOff(int32_t mode) = 0;
+
+    /* triggers screen on animation */
     virtual status_t turnElectronBeamOn(int32_t mode) = 0;
 
     /* verify that an ISurfaceTexture was created by SurfaceFlinger.
      */
     virtual bool authenticateSurfaceTexture(
             const sp<ISurfaceTexture>& surface) const = 0;
+
+    /* return an IDisplayEventConnection */
+    virtual sp<IDisplayEventConnection> createDisplayEventConnection() = 0;
 };
 
 // ----------------------------------------------------------------------------
@@ -151,6 +158,7 @@ public:
         TURN_ELECTRON_BEAM_OFF,
         TURN_ELECTRON_BEAM_ON,
         AUTHENTICATE_SURFACE,
+        CREATE_DISPLAY_EVENT_CONNECTION,
     };
 
     virtual status_t    onTransact( uint32_t code,
index b7e3ee3..b8be67d 100644 (file)
@@ -3,6 +3,8 @@ include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
        BitTube.cpp \
+       DisplayEventReceiver.cpp \
+       IDisplayEventConnection.cpp \
        ISensorEventConnection.cpp \
        ISensorServer.cpp \
        ISurfaceTexture.cpp \
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
new file mode 100644 (file)
index 0000000..3b29a11
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <string.h>
+
+#include <utils/Errors.h>
+
+#include <gui/BitTube.h>
+#include <gui/DisplayEventReceiver.h>
+#include <gui/IDisplayEventConnection.h>
+
+#include <private/gui/ComposerService.h>
+
+#include <surfaceflinger/ISurfaceComposer.h>
+
+// ---------------------------------------------------------------------------
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+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();
+}
+
+ssize_t DisplayEventReceiver::getEvents(DisplayEventReceiver::Event* events,
+        size_t count) {
+    ssize_t size = mDataChannel->read(events, sizeof(events[0])*count);
+    LOGE_IF(size<0,
+            "DisplayEventReceiver::getEvents error (%s)",
+            strerror(-size));
+    if (size >= 0) {
+        // Note: if (size % sizeof(events[0])) != 0, we've got a
+        // partial read. This can happen if the queue filed up (ie: if we
+        // didn't pull from it fast enough).
+        // We discard the partial event and rely on the sender to
+        // re-send the event if appropriate (some events, like VSYNC
+        // can be lost forever).
+
+        // returns number of events read
+        size /= sizeof(events[0]);
+    }
+    return size;
+}
+
+// ---------------------------------------------------------------------------
+
+}; // namespace android
diff --git a/libs/gui/IDisplayEventConnection.cpp b/libs/gui/IDisplayEventConnection.cpp
new file mode 100644 (file)
index 0000000..44127fb
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+#include <binder/Parcel.h>
+#include <binder/IInterface.h>
+
+#include <gui/IDisplayEventConnection.h>
+#include <gui/BitTube.h>
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+enum {
+    GET_DATA_CHANNEL = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpDisplayEventConnection : public BpInterface<IDisplayEventConnection>
+{
+public:
+    BpDisplayEventConnection(const sp<IBinder>& impl)
+        : BpInterface<IDisplayEventConnection>(impl)
+    {
+    }
+
+    virtual sp<BitTube> getDataChannel() const
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(IDisplayEventConnection::getInterfaceDescriptor());
+        remote()->transact(GET_DATA_CHANNEL, data, &reply);
+        return new BitTube(reply);
+    }
+};
+
+IMPLEMENT_META_INTERFACE(DisplayEventConnection, "android.gui.DisplayEventConnection");
+
+// ----------------------------------------------------------------------------
+
+status_t BnDisplayEventConnection::onTransact(
+    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+    switch(code) {
+        case GET_DATA_CHANNEL: {
+            CHECK_INTERFACE(IDisplayEventConnection, data, reply);
+            sp<BitTube> channel(getDataChannel());
+            channel->writeToParcel(reply);
+            return NO_ERROR;
+        } break;
+    }
+    return BBinder::onTransact(code, data, reply, flags);
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
index 86bc62a..db32827 100644 (file)
@@ -29,6 +29,9 @@
 
 #include <surfaceflinger/ISurfaceComposer.h>
 
+#include <gui/BitTube.h>
+#include <gui/IDisplayEventConnection.h>
+
 #include <ui/DisplayInfo.h>
 
 #include <gui/ISurfaceTexture.h>
@@ -44,6 +47,8 @@
 
 namespace android {
 
+class IDisplayEventConnection;
+
 class BpSurfaceComposer : public BpInterface<ISurfaceComposer>
 {
 public:
@@ -174,6 +179,27 @@ public:
         }
         return result != 0;
     }
+
+    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) {
+            LOGE("ISurfaceComposer::createDisplayEventConnection: error performing "
+                    "transaction: %s (%d)", strerror(-err), -err);
+            return result;
+        }
+        result = interface_cast<IDisplayEventConnection>(reply.readStrongBinder());
+        return result;
+    }
 };
 
 IMPLEMENT_META_INTERFACE(SurfaceComposer, "android.ui.ISurfaceComposer");
@@ -254,6 +280,12 @@ status_t BnSurfaceComposer::onTransact(
             int32_t result = authenticateSurfaceTexture(surfaceTexture) ? 1 : 0;
             reply->writeInt32(result);
         } break;
+        case CREATE_DISPLAY_EVENT_CONNECTION: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IDisplayEventConnection> connection(createDisplayEventConnection());
+            reply->writeStrongBinder(connection->asBinder());
+            return NO_ERROR;
+        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
index f63c0c1..95d651a 100644 (file)
@@ -2,19 +2,22 @@ LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-    Layer.cpp                                                          \
-    LayerBase.cpp                                                      \
-    LayerDim.cpp                                                       \
-    LayerScreenshot.cpp                                                \
-    DdmConnection.cpp                                          \
-    DisplayHardware/DisplayHardware.cpp        \
+    EventThread.cpp                         \
+    Layer.cpp                               \
+    LayerBase.cpp                           \
+    LayerDim.cpp                            \
+    LayerScreenshot.cpp                     \
+    DdmConnection.cpp                       \
+    DisplayHardware/DisplayHardware.cpp     \
     DisplayHardware/DisplayHardwareBase.cpp \
-    DisplayHardware/HWComposer.cpp                     \
-    GLExtensions.cpp                                           \
-    MessageQueue.cpp                                           \
-    SurfaceFlinger.cpp                                                 \
-    SurfaceTextureLayer.cpp                            \
-    Transform.cpp                                                      \
+    DisplayHardware/HWComposer.cpp          \
+    DisplayHardware/VSyncBarrier.cpp        \
+    DisplayEventConnection.cpp              \
+    GLExtensions.cpp                        \
+    MessageQueue.cpp                        \
+    SurfaceFlinger.cpp                      \
+    SurfaceTextureLayer.cpp                 \
+    Transform.cpp                           \
     
 
 LOCAL_CFLAGS:= -DLOG_TAG=\"SurfaceFlinger\"
diff --git a/services/surfaceflinger/DisplayEventConnection.cpp b/services/surfaceflinger/DisplayEventConnection.cpp
new file mode 100644 (file)
index 0000000..a0aa9c0
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <gui/IDisplayEventConnection.h>
+#include <gui/BitTube.h>
+#include <gui/DisplayEventReceiver.h>
+
+#include <utils/Errors.h>
+
+#include "SurfaceFlinger.h"
+#include "DisplayEventConnection.h"
+
+// ---------------------------------------------------------------------------
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+DisplayEventConnection::DisplayEventConnection(
+        const sp<SurfaceFlinger>& flinger)
+    : mFlinger(flinger), mChannel(new BitTube())
+{
+}
+
+DisplayEventConnection::~DisplayEventConnection() {
+    mFlinger->cleanupDisplayEventConnection(this);
+}
+
+void DisplayEventConnection::onFirstRef() {
+    // nothing to do here for now.
+}
+
+sp<BitTube> DisplayEventConnection::getDataChannel() const {
+    return mChannel;
+}
+
+status_t DisplayEventConnection::postEvent(const DisplayEventReceiver::Event& event)
+{
+    ssize_t size = mChannel->write(&event, sizeof(DisplayEventReceiver::Event));
+    return size < 0 ? status_t(size) : status_t(NO_ERROR);
+}
+
+
+// ---------------------------------------------------------------------------
+
+}; // namespace android
diff --git a/services/surfaceflinger/DisplayEventConnection.h b/services/surfaceflinger/DisplayEventConnection.h
new file mode 100644 (file)
index 0000000..46cf64b
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H
+#define ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <gui/IDisplayEventConnection.h>
+
+#include <utils/Errors.h>
+#include <gui/DisplayEventReceiver.h>
+
+// ---------------------------------------------------------------------------
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+class BitTube;
+class SurfaceFlinger;
+
+// ---------------------------------------------------------------------------
+
+class DisplayEventConnection : public BnDisplayEventConnection {
+public:
+    DisplayEventConnection(const sp<SurfaceFlinger>& flinger);
+
+    status_t postEvent(const DisplayEventReceiver::Event& event);
+
+private:
+    virtual ~DisplayEventConnection();
+    virtual void onFirstRef();
+    virtual sp<BitTube> getDataChannel() const;
+
+    sp<SurfaceFlinger> const mFlinger;
+    sp<BitTube> const mChannel;
+};
+
+// ---------------------------------------------------------------------------
+
+}; // namespace android
+
+// ---------------------------------------------------------------------------
+
+#endif /* ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H */
index f94d321..3bbc75e 100644 (file)
@@ -140,6 +140,7 @@ void DisplayHardware::init(uint32_t dpy)
     mDpiX = mNativeWindow->xdpi;
     mDpiY = mNativeWindow->ydpi;
     mRefreshRate = fbDev->fps;
+    mNextFakeVSync = 0;
 
 
 /* FIXME: this is a temporary HACK until we are able to report the refresh rate
@@ -152,6 +153,8 @@ void DisplayHardware::init(uint32_t dpy)
 #warning "refresh rate set via makefile to REFRESH_RATE"
 #endif
 
+    mRefreshPeriod = nsecs_t(1e9 / mRefreshRate);
+
     EGLint w, h, dummy;
     EGLint numConfigs=0;
     EGLSurface surface;
@@ -346,6 +349,37 @@ uint32_t DisplayHardware::getPageFlipCount() const {
     return mPageFlipCount;
 }
 
+// this needs to be thread safe
+nsecs_t DisplayHardware::waitForVSync() const {
+    nsecs_t timestamp;
+    if (mVSync.wait(&timestamp) < 0) {
+        // vsync not supported!
+        usleep( getDelayToNextVSyncUs(&timestamp) );
+    }
+    return timestamp;
+}
+
+int32_t DisplayHardware::getDelayToNextVSyncUs(nsecs_t* timestamp) const {
+    Mutex::Autolock _l(mFakeVSyncMutex);
+    const nsecs_t period = mRefreshPeriod;
+    const nsecs_t now = systemTime(CLOCK_MONOTONIC);
+    nsecs_t next_vsync = mNextFakeVSync;
+    nsecs_t sleep = next_vsync - now;
+    if (sleep < 0) {
+        // we missed, find where the next vsync should be
+        sleep = (period - ((now - next_vsync) % period));
+        next_vsync = now + sleep;
+    }
+    mNextFakeVSync = next_vsync + period;
+    timestamp[0] = next_vsync;
+
+    // round to next microsecond
+    int32_t sleep_us = (sleep + 999LL) / 1000LL;
+
+    // guaranteed to be > 0
+    return sleep_us;
+}
+
 status_t DisplayHardware::compositionComplete() const {
     return mNativeWindow->compositionComplete();
 }
index f02c954..45d4b45 100644 (file)
@@ -32,6 +32,7 @@
 #include "GLExtensions.h"
 
 #include "DisplayHardware/DisplayHardwareBase.h"
+#include "DisplayHardware/VSyncBarrier.h"
 
 namespace android {
 
@@ -74,6 +75,9 @@ public:
     uint32_t    getMaxTextureSize() const;
     uint32_t    getMaxViewportDims() const;
 
+    // waits for the next vsync and returns the timestamp of when it happened
+    nsecs_t        waitForVSync() const;
+
     uint32_t getPageFlipCount() const;
     EGLDisplay getEGLDisplay() const { return mDisplay; }
 
@@ -95,6 +99,7 @@ public:
 private:
     void init(uint32_t displayIndex) __attribute__((noinline));
     void fini() __attribute__((noinline));
+    int32_t getDelayToNextVSyncUs(nsecs_t* timestamp) const;
 
     sp<SurfaceFlinger> mFlinger;
     EGLDisplay      mDisplay;
@@ -112,7 +117,12 @@ private:
     mutable uint32_t mPageFlipCount;
     GLint           mMaxViewportDims[2];
     GLint           mMaxTextureSize;
-    
+    VSyncBarrier    mVSync;
+
+    mutable Mutex   mFakeVSyncMutex;
+    mutable nsecs_t mNextFakeVSync;
+    nsecs_t         mRefreshPeriod;
+
     HWComposer*     mHwc;
 
     sp<FramebufferNativeWindow> mNativeWindow;
diff --git a/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp b/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp
new file mode 100644 (file)
index 0000000..187da20
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/fb.h>
+
+#include "DisplayHardware/VSyncBarrier.h"
+
+#ifndef FBIO_WAITFORVSYNC
+#define FBIO_WAITFORVSYNC   _IOW('F', 0x20, __u32)
+#endif
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+VSyncBarrier::VSyncBarrier() : mFd(-EINVAL) {
+#if HAS_WAITFORVSYNC
+    mFd = open("/dev/graphics/fb0", O_RDWR);
+    if (mFd < 0) {
+        mFd = -errno;
+    }
+    // try to see if FBIO_WAITFORVSYNC is supported
+    uint32_t crt = 0;
+    int err = ioctl(mFd, FBIO_WAITFORVSYNC, &crt);
+    if (err < 0) {
+        close(mFd);
+        mFd = -EINVAL;
+    }
+#endif
+}
+
+VSyncBarrier::~VSyncBarrier() {
+    if (mFd >= 0) {
+        close(mFd);
+    }
+}
+
+status_t VSyncBarrier::initCheck() const {
+    return mFd < 0 ? mFd : status_t(NO_ERROR);
+}
+
+// this must be thread-safe
+status_t VSyncBarrier::wait(nsecs_t* timestamp) const {
+    if (mFd < 0) {
+        return mFd;
+    }
+
+    int err;
+    uint32_t crt = 0;
+    do {
+        err = ioctl(mFd, FBIO_WAITFORVSYNC, &crt);
+    } while (err<0 && errno==EINTR);
+    if (err < 0) {
+        return -errno;
+    }
+    // ideally this would come from the driver
+    timestamp[0] = systemTime();
+    return NO_ERROR;
+}
+
+// ---------------------------------------------------------------------------
+}; // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/VSyncBarrier.h b/services/surfaceflinger/DisplayHardware/VSyncBarrier.h
new file mode 100644 (file)
index 0000000..3c32950
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_
+#define ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+class VSyncBarrier {
+    int mFd;
+public:
+    VSyncBarrier();
+    ~VSyncBarrier();
+    status_t initCheck() const;
+    status_t wait(nsecs_t* timestamp) const;
+};
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+
+#endif /* ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_ */
diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp
new file mode 100644 (file)
index 0000000..edb06ba
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <gui/IDisplayEventConnection.h>
+#include <gui/DisplayEventReceiver.h>
+
+#include <utils/Errors.h>
+
+#include "DisplayHardware/DisplayHardware.h"
+#include "DisplayEventConnection.h"
+#include "EventThread.h"
+#include "SurfaceFlinger.h"
+
+// ---------------------------------------------------------------------------
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+EventThread::EventThread(const sp<SurfaceFlinger>& flinger)
+    : mFlinger(flinger),
+      mHw(flinger->graphicPlane(0).displayHardware()),
+      mDeliveredEvents(0)
+{
+}
+
+void EventThread::onFirstRef() {
+    run("EventThread", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);
+}
+
+status_t EventThread::registerDisplayEventConnection(
+        const sp<DisplayEventConnection>& connection) {
+    Mutex::Autolock _l(mLock);
+    mDisplayEventConnections.add(connection);
+    mCondition.signal();
+    return NO_ERROR;
+}
+
+status_t EventThread::unregisterDisplayEventConnection(
+        const wp<DisplayEventConnection>& connection) {
+    Mutex::Autolock _l(mLock);
+    mDisplayEventConnections.remove(connection);
+    mCondition.signal();
+    return NO_ERROR;
+}
+
+bool EventThread::threadLoop() {
+
+    nsecs_t timestamp;
+    Mutex::Autolock _l(mLock);
+    do {
+        // wait for listeners
+        while (!mDisplayEventConnections.size()) {
+            mCondition.wait(mLock);
+        }
+
+        // wait for vsync
+        mLock.unlock();
+        timestamp = mHw.waitForVSync();
+        mLock.lock();
+
+        // make sure we still have some listeners
+    } while (!mDisplayEventConnections.size());
+
+
+    // dispatch vsync events to listeners...
+    mDeliveredEvents++;
+    const size_t count = mDisplayEventConnections.size();
+
+    DisplayEventReceiver::Event vsync;
+    vsync.header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
+    vsync.header.timestamp = timestamp;
+    vsync.vsync.count = mDeliveredEvents;
+
+    for (size_t i=0 ; i<count ; i++) {
+        sp<DisplayEventConnection> conn(mDisplayEventConnections.itemAt(i).promote());
+        // make sure the connection didn't die
+        if (conn != NULL) {
+            status_t err = conn->postEvent(vsync);
+            if (err == -EAGAIN || err == -EWOULDBLOCK) {
+                // The destination doesn't accept events anymore, it's probably
+                // full. For now, we just drop the events on the floor.
+                // Note that some events cannot be dropped and would have to be
+                // re-sent later. Right-now we don't have the ability to do
+                // this, but it doesn't matter for VSYNC.
+            } else if (err < 0) {
+                // handle any other error on the pipe as fatal. the only
+                // reasonable thing to do is to clean-up this connection.
+                // The most common error we'll get here is -EPIPE.
+                mDisplayEventConnections.remove(conn);
+            }
+        }
+    }
+
+    return true;
+}
+
+status_t EventThread::readyToRun() {
+    LOGI("EventThread ready to run.");
+    return NO_ERROR;
+}
+
+void EventThread::dump(String8& result, char* buffer, size_t SIZE) const {
+    Mutex::Autolock _l(mLock);
+    result.append("VSYNC state:\n");
+    snprintf(buffer, SIZE, "  numListeners=%u, events-delivered: %u\n",
+            mDisplayEventConnections.size(), mDeliveredEvents);
+    result.append(buffer);
+}
+
+// ---------------------------------------------------------------------------
+
+}; // namespace android
diff --git a/services/surfaceflinger/EventThread.h b/services/surfaceflinger/EventThread.h
new file mode 100644 (file)
index 0000000..0482ab7
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef ANDROID_SURFACE_FLINGER_EVENT_THREAD_H
+#define ANDROID_SURFACE_FLINGER_EVENT_THREAD_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <gui/IDisplayEventConnection.h>
+
+#include <utils/Errors.h>
+#include <utils/threads.h>
+#include <utils/SortedVector.h>
+
+#include "DisplayEventConnection.h"
+
+// ---------------------------------------------------------------------------
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+class SurfaceFlinger;
+class DisplayHardware;
+
+// ---------------------------------------------------------------------------
+
+class EventThread : public Thread {
+    friend class DisplayEventConnection;
+
+public:
+    EventThread(const sp<SurfaceFlinger>& flinger);
+
+    status_t registerDisplayEventConnection(
+            const sp<DisplayEventConnection>& connection);
+
+    status_t unregisterDisplayEventConnection(
+            const wp<DisplayEventConnection>& connection);
+
+    void dump(String8& result, char* buffer, size_t SIZE) const;
+
+private:
+    virtual bool        threadLoop();
+    virtual status_t    readyToRun();
+    virtual void        onFirstRef();
+
+    // constants
+    sp<SurfaceFlinger> mFlinger;
+    const DisplayHardware& mHw;
+
+    mutable Mutex mLock;
+    mutable Condition mCondition;
+
+    // protected by mLock
+    SortedVector<wp<DisplayEventConnection> > mDisplayEventConnections;
+    size_t mDeliveredEvents;
+};
+
+// ---------------------------------------------------------------------------
+
+}; // namespace android
+
+// ---------------------------------------------------------------------------
+
+#endif /* ANDROID_SURFACE_FLINGER_EVENT_THREAD_H */
index 39f0f40..d5a8d08 100644 (file)
@@ -34,6 +34,8 @@
 #include <binder/MemoryHeapBase.h>
 #include <binder/PermissionCache.h>
 
+#include <gui/IDisplayEventConnection.h>
+
 #include <utils/String8.h>
 #include <utils/String16.h>
 #include <utils/StopWatch.h>
@@ -46,6 +48,8 @@
 #include <GLES/gl.h>
 
 #include "clz.h"
+#include "DisplayEventConnection.h"
+#include "EventThread.h"
 #include "GLExtensions.h"
 #include "DdmConnection.h"
 #include "Layer.h"
@@ -293,12 +297,16 @@ status_t SurfaceFlinger::readyToRun()
     // put the origin in the left-bottom corner
     glOrthof(0, w, 0, h, 0, 1); // l=0, r=w ; b=0, t=h
 
-    mReadyToRunBarrier.open();
+
+    // start the EventThread
+    mEventThread = new EventThread(this);
 
     /*
      *  We're now ready to accept clients...
      */
 
+    mReadyToRunBarrier.open();
+
     // start boot animation
     property_set("ctl.start", "bootanim");
 
@@ -319,6 +327,22 @@ void SurfaceFlinger::signalEvent() {
     mEventQueue.invalidate();
 }
 
+status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg,
+        nsecs_t reltime, uint32_t flags) {
+    return mEventQueue.postMessage(msg, reltime);
+}
+
+status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg,
+        nsecs_t reltime, uint32_t flags) {
+    status_t res = mEventQueue.postMessage(msg, reltime);
+    if (res == NO_ERROR) {
+        msg->wait();
+    }
+    return res;
+}
+
+// ----------------------------------------------------------------------------
+
 bool SurfaceFlinger::authenticateSurfaceTexture(
         const sp<ISurfaceTexture>& surfaceTexture) const {
     Mutex::Autolock _l(mStateLock);
@@ -360,20 +384,17 @@ bool SurfaceFlinger::authenticateSurfaceTexture(
     return false;
 }
 
-status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg,
-        nsecs_t reltime, uint32_t flags)
-{
-    return mEventQueue.postMessage(msg, reltime, flags);
+// ----------------------------------------------------------------------------
+
+sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection() {
+    sp<DisplayEventConnection> result(new DisplayEventConnection(this));
+    mEventThread->registerDisplayEventConnection(result);
+    return result;
 }
 
-status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg,
-        nsecs_t reltime, uint32_t flags)
-{
-    status_t res = mEventQueue.postMessage(msg, reltime, flags);
-    if (res == NO_ERROR) {
-        msg->wait();
-    }
-    return res;
+void SurfaceFlinger::cleanupDisplayEventConnection(
+        const wp<DisplayEventConnection>& connection) {
+    mEventThread->unregisterDisplayEventConnection(connection);
 }
 
 // ----------------------------------------------------------------------------
@@ -432,7 +453,7 @@ bool SurfaceFlinger::threadLoop()
     } else {
         // pretend we did the post
         hw.compositionComplete();
-        usleep(16667); // 60 fps period
+        hw.waitForVSync();
     }
     return true;
 }
@@ -1572,9 +1593,16 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args)
         }
 
         /*
+         * VSYNC state
+         */
+        mEventThread->dump(result, buffer, SIZE);
+
+        /*
          * Dump HWComposer state
          */
         HWComposer& hwc(hw.getHwComposer());
+        snprintf(buffer, SIZE, "h/w composer state:\n");
+        result.append(buffer);
         snprintf(buffer, SIZE, "  h/w composer %s and %s\n",
                 hwc.initCheck()==NO_ERROR ? "present" : "not present",
                 (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled");
index 17028db..1039f47 100644 (file)
@@ -46,6 +46,8 @@ namespace android {
 
 class Client;
 class DisplayHardware;
+class DisplayEventConnection;
+class EventThread;
 class Layer;
 class LayerDim;
 class LayerScreenshot;
@@ -171,6 +173,7 @@ public:
                                                             int orientation, uint32_t flags);
     virtual int                         setOrientation(DisplayID dpy, int orientation, uint32_t flags);
     virtual bool                        authenticateSurfaceTexture(const sp<ISurfaceTexture>& surface) const;
+    virtual sp<IDisplayEventConnection> createDisplayEventConnection();
 
     virtual status_t captureScreen(DisplayID dpy,
             sp<IMemoryHeap>* heap,
@@ -222,6 +225,7 @@ private:
 
 private:
     friend class Client;
+    friend class DisplayEventConnection;
     friend class LayerBase;
     friend class LayerBaseClient;
     friend class Layer;
@@ -331,6 +335,9 @@ private:
             status_t electronBeamOffAnimationImplLocked();
             status_t electronBeamOnAnimationImplLocked();
 
+            void cleanupDisplayEventConnection(
+                    const wp<DisplayEventConnection>& connection);
+
             void        debugFlashRegions();
             void        debugShowFPS() const;
             void        drawWormhole() const;
@@ -361,6 +368,7 @@ private:
                 GLuint                      mWormholeTexName;
                 GLuint                      mProtectedTexName;
                 nsecs_t                     mBootTime;
+                sp<EventThread>             mEventThread;
 
                 // Can only accessed from the main thread, these members
                 // don't need synchronization
diff --git a/services/surfaceflinger/tests/vsync/Android.mk b/services/surfaceflinger/tests/vsync/Android.mk
new file mode 100644 (file)
index 0000000..9181760
--- /dev/null
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+       vsync.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+       libcutils \
+       libutils \
+       libbinder \
+    libui \
+    libgui
+
+LOCAL_MODULE:= test-vsync-events
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/surfaceflinger/tests/vsync/vsync.cpp b/services/surfaceflinger/tests/vsync/vsync.cpp
new file mode 100644 (file)
index 0000000..4f79080
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Looper.h>
+
+using namespace android;
+
+int receiver(int fd, int events, void* data)
+{
+    DisplayEventReceiver* q = (DisplayEventReceiver*)data;
+
+    ssize_t n;
+    DisplayEventReceiver::Event buffer[1];
+
+    static nsecs_t oldTimeStamp = 0;
+
+    while ((n = q->getEvents(buffer, 1)) > 0) {
+        for (int i=0 ; i<n ; i++) {
+            if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+                printf("event vsync: count=%d\t", buffer[i].vsync.count);
+            }
+            if (oldTimeStamp) {
+                float t = float(buffer[i].header.timestamp - oldTimeStamp) / s2ns(1);
+                printf("%f ms (%f Hz)\n", t*1000, 1.0/t);
+            }
+            oldTimeStamp = buffer[i].header.timestamp;
+        }
+    }
+    if (n<0) {
+        printf("error reading events (%s)\n", strerror(-n));
+    }
+    return 1;
+}
+
+int main(int argc, char** argv)
+{
+    DisplayEventReceiver myDisplayEvent;
+
+
+    sp<Looper> loop = new Looper(false);
+    loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver,
+            &myDisplayEvent);
+
+    do {
+        //printf("about to poll...\n");
+        int32_t ret = loop->pollOnce(-1);
+        switch (ret) {
+            case ALOOPER_POLL_WAKE:
+                //("ALOOPER_POLL_WAKE\n");
+                break;
+            case ALOOPER_POLL_CALLBACK:
+                //("ALOOPER_POLL_CALLBACK\n");
+                break;
+            case ALOOPER_POLL_TIMEOUT:
+                printf("ALOOPER_POLL_TIMEOUT\n");
+                break;
+            case ALOOPER_POLL_ERROR:
+                printf("ALOOPER_POLL_TIMEOUT\n");
+                break;
+            default:
+                printf("ugh? poll returned %d\n", ret);
+                break;
+        }
+    } while (1);
+
+    return 0;
+}
diff --git a/services/surfaceflinger/tests/waitforvsync/Android.mk b/services/surfaceflinger/tests/waitforvsync/Android.mk
new file mode 100644 (file)
index 0000000..c25f5ab
--- /dev/null
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+       waitforvsync.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+       libcutils \
+
+LOCAL_MODULE:= test-waitforvsync
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp b/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp
new file mode 100644 (file)
index 0000000..279b88b
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/fb.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef FBIO_WAITFORVSYNC
+#define FBIO_WAITFORVSYNC   _IOW('F', 0x20, __u32)
+#endif
+
+int main(int argc, char** argv) {
+    int fd = open("/dev/graphics/fb0", O_RDWR);
+    if (fd >= 0) {
+        do {
+            uint32_t crt = 0;
+           int err = ioctl(fd, FBIO_WAITFORVSYNC, &crt);
+           if (err < 0) {
+               printf("FBIO_WAITFORVSYNC error: %s\n", strerror(errno));
+               break;
+           }
+        } while(1);
+        close(fd);
+    }
+    return 0;
+}