Android 5.1 SurfaceFlinger VSYNC详解

时间:2022-05-17 03:55:33

原文网址(转载请注明出处):
http://blog.csdn.net/newchenxf/article/details/49131167

VSYNC是自android 4.1版本以后加入的,目的是为了改善android的流畅程度。
其实网上已经有不少朋友对android SurfaceFlinger的VSYNC做分析,比如下面的这些文章,本人也是阅读了很多别人家的文章后,深读代码,加log调试研究,才写的,感谢他们。
1. Android 4.4(KitKat)中的设计模式-Graphics子系统
http://blog.csdn.net/jinzhuojun/article/details/17427491
2. Android 4.4(KitKat)中VSync信号的虚拟化
http://blog.csdn.net/jinzhuojun/article/details/17293325
3. android4.1及后续版本的UI平滑技术
http://blog.csdn.net/sfrysh/article/details/8312766
4. Android应用程序UI硬件加速渲染环境初始化过程分析
http://blog.csdn.net/luoshengyang/article/details/45769759

现在Android已经到了6.0,不过我还没拉到6.0的代码,所以就先用5.1版本做分析。好了,现在我们开始。
我们将分3部分来讲解。一是什么是VSYNC,一是VSYNC如何从hardware传到surfaceflinger,一是VSYNC如何从surfaceflinger传到application。

一、什么是VSYNC

VSYNC是啥?他就是一个信号源。假设你的屏幕是60FPS的话,那意味着,每隔1s,那么屏幕就有60次中断信号产生,即,每隔16.666ms,就会有一次中断信号产生。这个能干嘛用呢?对于屏幕来说,它无非就是每次来一个中断信号,就刷新一下屏幕。
在Android 4.1以前,VSYNC和android没有半毛钱关系。但到了4.4及以后,android就引入了VSYNC,干嘛用呢?为的就是利用这个中断信号,来刷新UI,使得更新UI变得更加有序,流畅。

加入VSYNC以后,UI是如何画图和更新的呢?
首先,我们必须明白,android graphics的主干就是2件事:
一个是画图,一个是合成显示。
画图 需要android app调用Canvas API去画。至于最后是纯软件画,还是用OpenGL ES,也就是硬件加速去画,那就看具体情况了。这里不详说。
合成显示 需要surfaceflinger拿到app画好的几张图(因为当前可见的UI不只一个app,某个app也不一定只有一张图,所以说是,几张图,其实对于framework来说,图也可以认为是一个Surface,某一个最最普通的activity,它会有个layout对吧,那其实会对应一个Surface,当然了,如果你这layout里还有SurfaceView控件,那这个activity就有2个Surface了),合成为一张,然后送出去。如果你是机顶盒平台,那就是通过HDMI的驱动,输出到电视。如果你是某个android手机,那应该是送到linux的framebuffer driver,该驱动会把数据render到屏幕上。

那么,VSYNC作用就来了。
Android 5.1 SurfaceFlinger VSYNC详解

如图所示,Display代表合成显示,CPU/GPU代表画图。
在第1个VSYNC时间,CPU/GPU画图,Display还没拿到图,不显示。
第2个VSYNC时间,图1画好了,Display拿到,就显示它,同时,CPU/GPU也开始画第2个图。
第3个VSYNC时间,图2画好了,Display拿到,就显示它,同时,CPU/GPU也开始画第3个图。
如此往复。
当然了,上图是一个示意图,实际使用时,会用tripple buffer的机制,具体详见【android4.1及后续版本的UI平滑技术】。
另外,实际工作也会加上一定的偏移。
Android 5.1 SurfaceFlinger VSYNC详解
虽然有偏移,但大家工作既保持一定的节拍,又可以相互错开,一前一后保持着咚次哒次,也挺好的,还可以让CPU能错开工作高峰。其中两个Phase offset参数(即VSYNC_EVENT_PHASE_OFFSET_NS和SF_VSYNC_EVENT_PHASE_OFFSET_NS)是可调的。
具体详见【Android 4.4(KitKat)中VSync信号的虚拟化

总而言之,有2个地方需要用VSYNC,一个是surfaceflinger,每来一个VSYNC,就合成显示一次。一个是application,每来一个VSYNC,就画一张图。

二、VSYNC如何从hardware传到surfaceflinger

理论上,VSYNC是硬件产生的,是一个中断信号。比如,我当前做的机顶盒项目,这中断信号是HDMI产生的,我的电视是60FPS,因此每16.666..ms就有一个中断。一般ODM厂商已有驱动程序,会接收该中断,其他进程可以通过ioctl来间接接收该中断。假设该驱动是disp_driver。
我们知道,Android Graphics有一个 HWComposer HAL库,这是ODM实现的。其实,这个库也要完成VSYNC的工作。它通过与disp_driver通信,去监听硬件产生的VSYNC中断,收到以后,立马通过callback函数,返回给android framework 的HWComposer。当然啦,framework的HWComposer也得向它的HAL注册callback,不然HAL就没法回调了。
注册函数是:
hwc_composer_device_1 -> registerProcs
VSYNC的回调函数是:
HWComposer -> hook_vsync

对android来说,Graphics的核心进程时surfaceflinger,它主要用于合成各种surface并显示。HWComposer HAL是它加载的。显然HAL的VSYNC callback也是给他的。

surfaceflinger有4个线程与VSYNC有关。
1个EventControlThread,用于开始/关闭 HWComposer HAL的VSYNC。
1个是DispSyncThread,用于接收HWComposer HAL的VSYNC信号,并分发给EventThread。
2个EventThread。之所以是2个,是因为surfaceflinger创建了2个EventThread对象,一个给application用,一个给自己用。如上文所说,application用它来画图,surfaceflinger用它来显示。EventThread类主要是接收DispSyncThread发来的VSYNC,并分发给真正使用的人。
这些线程是在surfaceflinger初始化的时候创建的。
代码如下:

void SurfaceFlinger::init() {
......
    // start the EventThread
    //mPrimaryDispSync is DispSync
    sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            vsyncPhaseOffsetNs, true, "app");
    mEventThread = new EventThread(vsyncSrc);
    sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            sfVsyncPhaseOffsetNs, true, "sf");
    mSFEventThread = new EventThread(sfVsyncSrc);
    mEventQueue.setEventThread(mSFEventThread);

    mEventControlThread = new EventControlThread(this);
    mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
}

DispSync::DispSync() :
        mRefreshSkipCount(0),
        mThread(new DispSyncThread()) {
    //start DispSyncThread
    mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);

从代码可知,2个EventThread对象分别是mEventThread,给app用,mSFEventThread,给surfaceflinger自己用。

下面给出这4个Thread关系图。
Android 5.1 SurfaceFlinger VSYNC详解
从这个图,我们可以清晰的看出VSYNC的传递过程。

HWComposer HAL通过callback函数,把VSYNC信号传给DispSyncThread,DispSyncThread传给EventThread。这就是本节要讨论的,VSYNC如何从hardware传到android framework。
postEvent是干嘛呢?它用于把VSYNC传递给app,是下节要讨论的内容。这里先简单描述一下:
如果是app用的话,那么,app会向EventThread注册一个connection(每个app启动时,都会注册)。所以该EventThread会向所有的connection发送VSYNC信号(通过postEvent 函数)。
而如果是surfaceflinger自己用的话,就简单了,就自己一个connection。
上面代码的mEventQueue.setEventThread(mSFEventThread)就是surfaceflinger自己注册connection的。
且看该函数的实现

void MessageQueue::setEventThread(const sp<EventThread>& eventThread)
{
    mEventThread = eventThread;
    mEvents = eventThread->createEventConnection();
    mEventTube = mEvents->getDataChannel();
    mLooper->addFd(mEventTube->getFd(), 0, Looper::EVENT_INPUT,
            MessageQueue::cb_eventReceiver, this);
}

通过createEventConnection向EventThread注册了connection。
surfaceflinger如何根据vsync的节奏来工作,比较简单,和app如何根据vsync的节奏来工作差不多,所以本文只讨论app如何使用vsync,也就是第三节的内容。

前面的模块图毕竟比较简约,我这里给出详细的,VSYNC如何从HWComposer HAL发到EventThread。
Android 5.1 SurfaceFlinger VSYNC详解
从图中可知,HWComposer HAL层获得硬件中断,然后通过callback函数,回调到SurfaceFlinger的onVsyncReceived。
SurfaceFlinger调用DispSync的updateModel函数,唤醒DispSyncThread。
DispSyncThread通过fireCallbackInvocations函数,告诉DispSyncSource有VSYNC了。

    void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
        for (size_t i = 0; i < callbacks.size(); i++) {
            callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
        }
    }

mCallback就是DispSyncSource,因为他继承了DispSync::Callback。
所以,就调用到了DispSyncSource的onDispSyncEvent函数。

    virtual void onDispSyncEvent(nsecs_t when) {
        sp<VSyncSource::Callback> callback;
        {
            Mutex::Autolock lock(mMutex);
            callback = mCallback;
            ......
        }
        if (callback != NULL) {
            callback->onVSyncEvent(when);
        }
    }

对DispSyncSource来说,它的mCallback成员变量,是EventThread(EventThread继承了VSyncSource::Callback)。
所以,又调用了EventThread的onVSyncEvent函数。
这么说来,其实我们可以把DispSyncSource理解为DispSyncThread与EventThread的桥梁。

接着,来看onVSyncEvent。

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();
}

他也比较简单,存下vsync,然后唤醒EventThread。
EventThread的threadLoop唤醒后,通过postEvent,向所有注册过的connection发送VSYNC消息。

VSYNC从surfaceflinger到app

上一节讲了VSYNC从HAL到framework(其实就是surfaceflinger),这一节就讲讲VSYNC从surfaceflinger到app。
其实就是那个给app用的EventThread如何把VSYNC送到app。

首先,每个app创建时,都会new一个RenderThread(有且只有一个)。看名字就知道,它用于管理app UI 。
它初始化的时候,会new DisplayEventReceiver

void RenderThread::initializeDisplayEventReceiver() {
    ......
    mDisplayEventReceiver = new DisplayEventReceiver();
    ......

    // Register the FD
    mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
            Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
}

DisplayEventReceiver是啥?
看名字就知道,它用于接收VSYNC。
首先,对于EventThread,其connection是这么定义的。
它是一个binder调用。
API定义在IDisplayEventConnection,总共就3个函数。

class IDisplayEventConnection : public IInterface
{
public:
    /*
     * getDataChannel() returns a BitTube where to receive the events from
     */
    virtual sp<BitTube> getDataChannel() const = 0;

    virtual void setVsyncRate(uint32_t count) = 0;

    virtual void requestNextVsync() = 0;
};

服务端的具体实现是Connection类,它继承了BnDisplayEventConnection ,并具体实现了3个函数。
定义在frameworks/native/services/surfaceflinger/EventThread.h

class Connection : public BnDisplayEventConnection {
public:
    Connection(const sp<EventThread>& eventThread);
    status_t postEvent(const DisplayEventReceiver::Event& event);
        ....
private:
    virtual ~Connection();
    virtual void onFirstRef();
    virtual sp<BitTube> getDataChannel() const;
    virtual void setVsyncRate(uint32_t count);
    virtual void requestNextVsync();    // asynchronous
    sp<EventThread> const mEventThread;
    sp<BitTube> const mChannel;
};

这个类最重要的成员变量,就是mChannel,它是BitTube类。BitTube是啥?
字面意思就是字节管道。如果看它的构造函数,就能发现,其实它就是包了socket,用于进程间通信。

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));
    }
}

DisplayEventReceiver构造函数,就首先会调用surfaceflinger去注册一个connection,connection又会创建BitTube。客户端便可以通过getDataChannel()获得这个BitTube。
那么进程通信就可以开始了。
surfaceflinger进程的EventThread把VSYNC不断的往BitTube写(想象成往socket写),而app进程将会创建DisplayEventReceiver,该app进程又可以不断的读BitTube(想象成读socket)。这样,VSYNC就可以传输了。

那么,谁会创建DisplayEventReceiver呢?是传说中的RenderThread。
如上文所说,每个app创建,都会new一个RenderThread,用于管理UI的画图。如同上面讲的4个线程, 这个RenderThread也是继承Thread类,当我们调用它的成员函数run的时候,就会创建一个新的线程。这个新的线程的入口点函数为RenderThread类的成员函数threadLoop。

RenderThread类有一个成员变量mQueue,描述的是一个Task Queue。这个Queue用于保存所有需要执行的Task,每一个Task都是用一个RenderTask对象来描述。同时,RenderTask类有一个成员变量mRunAt,用来表明Task的执行时间。这样,保存在Task Queue的Task就可以按照执行时间从先到后的顺序排序,这个mRunAt最终会存VSYNC。于是,RenderThread类的成员函数nextTask通过判断排在队列头的Task的执行时间是否小于等于当前时间,就可以知道当前是否有Task需要执行。如果有Task需要执行的话,就将它返回给调用者。我们可以把Task当做是画图任务。

这里给出RenderThread创建DisplayEventReceiver的代码。

void RenderThread::initializeDisplayEventReceiver() {
    LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
    mDisplayEventReceiver = new DisplayEventReceiver();
    status_t status = mDisplayEventReceiver->initCheck();
    LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver "
            "failed with status: %d", status);

    // Register the FD
    mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
            Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
}

这个函数的一个语句很重要:
mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
它表示,监听一个文件描述符,如果有变化,就执行回调函数displayEventReceiverCallback。
那么,mDisplayEventReceiver->getFd()获得的文件描述符是啥?
且看定义

int DisplayEventReceiver::getFd() const {
    //mDataChanel is BitTube
    if (mDataChannel == NULL)        
        return NO_INIT;
    return mDataChannel->getFd();
}

再看BitTube的定义

int BitTube::getFd() const
{
    return mReceiveFd;
}

啊,原来,这个mReveivedRd就是sockets[0]。

而回调函数displayEventReceiverCallback干嘛呢?查看函数的调用顺序,就可以看到,其实它就是去读sockets[0]。
所以一切就明白了,surfaceflinger进程与某个app进程传递VSYNC,就是surfaceflinger的EventThread线程与app的RenderThread通过socket通信来传递。
下面给出流程图。
Android 5.1 SurfaceFlinger VSYNC详解
那么,RenderThread通过displayEventReceiverCallback回调,获得VSYNC后,会干嘛呢?当然就是画图啦!!
文章【Android应用程序UI硬件加速渲染环境初始化过程分析】有比较详细的介绍,我这里忽略一些条件判断等各种细节,大概描述一下主体流程:
displayEventReceiverCallback会调用drainDisplayEventQueue,请看这个函数。

void RenderThread::drainDisplayEventQueue() {
    ATRACE_CALL();
    nsecs_t vsyncEvent = latestVsyncEvent(mDisplayEventReceiver);
    if (vsyncEvent > 0) {
        mVsyncRequested = false;
        if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) {
            ATRACE_NAME("queue mFrameCallbackTask");
            mFrameCallbackTaskPending = true;
            nsecs_t runAt = (vsyncEvent + DISPATCH_FRAME_CALLBACKS_DELAY);
            queueAt(mFrameCallbackTask, runAt);
        }
    }
}

首先通过latestVsyncEvent获得最新发出的VSYNC时间,然后,通过queueAt,把mFrameCallbackTask及对应的VSYNC时间插入Task Queue,并调用“mLooper->wake();”唤醒线程。
来看线程函数:

bool RenderThread::threadLoop() {
    ......
    initThreadLocals();

    int timeoutMillis = -1;
    for (;;) {
        int result = mLooper->pollOnce(timeoutMillis);
        LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
                "RenderThread Looper POLL_ERROR!");

        nsecs_t nextWakeup;
        // Process our queue, if we have anything
        while (RenderTask* task = nextTask(&nextWakeup)) {
            task->run();
            // task may have deleted itself, do not reference it again
        }
        ......
    }
}

线程唤醒后,执行task->run()。他会干嘛呢?
查看其定义知,就是调用
mRenderThread->dispatchFrameCallbacks()。
而这个函数就是真正去调用画图啦!
代码片段如下,doFrame()是真正干活的函数。

void RenderThread::dispatchFrameCallbacks() {
    ......
    for (std::set<IFrameCallback*>::iterator it = callbacks.begin(); it != callbacks.end(); it++) {
        (*it)->doFrame();
    }
}

总结一下就是。
1. App进程创建了RenderThread对象,RenderThread又创建了DisplayEventReceiver对象。
2. DisplayEventReceiver获得surfaceflinger的client handle,然后通过函数createDisplayEventConnection,向EventThread注册一个IDisplayEventConnection。这其实是binder调用。
3. DisplayEventReceiver通过IDisplayEventConnection->getDataChannel()获得BitTube对象,该对象包了socket管道的文件句柄,用于后面两个进程传递VSYNC。
4. 这些都准备好以后,一切都开始咚次哒次的跑起来。SurfaceFlinger的EventThread线程通过socket与App的RenderThread传递VSYNC时间。RenderThread获得VSYNC后,根据VSYNC的节拍,通过调用doFrame去最终画图。

结语

android graphics非常复杂庞大,VSYNC只是冰山一角。未来还有更多的知识值得学习。。。