Vsync垂直同步信号分发和SurfaceFlinger响应执行渲染流程分析(一)

     之前有一篇博客已经讲过一点Android应用程序和SurfaceFlinger建立连接,获取一个binder本地代理对象的过程,有了这个本地代理对象,应用程序就可以使用它调用SurfaceFlinger的UI合成、渲染功能了,可参考:Android Application与SurfaceFlinger连接过程分析,讲的很粗糙,但是我们可以看到一个大致过程,就是在WindowManagerService为每个Activity执行添加窗口addView方法时,创建好了WindowState对象,然后会调用win.attch()方法,和SurfaceFlinger取得连接也就是这个时候建立的。如果大家还想了解更细的话,可以参考老罗的博客:Android应用程序与SurfaceFlinger服务的连接过程分析,这里讲的非常详细。

     关于UI合成渲染这块的知识,用到的东西太多了,我也是平时看看书,查查百度,想尽量搞清楚一些,但是目前为止,还是只能了解的部分,无法从全面掌握,也是为了怕自己忘记,所以将自己学习到的总结出来,方便随时查阅,如果能给大家带来帮助,那就更好了。

     好了,我们进入正题,话说Android在4.1版本之前,UI界面方面存在很大的一个问题,就是经常会出现界面错乱,Google官方叫Jank,我们来看看下面这幅图:

Vsync垂直同步信号分发和SurfaceFlinger响应执行渲染流程分析(一)_第1张图片

     从图上我们就很明显可以看出来问题产生的原因了,就是我们的CPU、GPU、Display三个器件需要进行配合对所有的UI进行合成、渲染、显示,但是之前的版本当中,没有一个统一的命令,导致CPU不知道何时开始执行合成,于是推迟了时间,后边的渲染和显示自然也就耽误了,那么就会出现数字1的画面显示了两帧,还有可能出现,数字1的一部分和数字2的一部分显示在同一界面上了,Google为了解决这个问题,所以加入了Vsync垂直同步信息,我们来看看加入了Vsync垂直同步信息后的情况:

Vsync垂直同步信号分发和SurfaceFlinger响应执行渲染流程分析(一)_第2张图片

     从加入了垂直同步信号后的情况明显可以看出来,情况好了很多,由信号产生模块发出信号,只要信号发出,CPU马上进行合成,完成后交给GPU渲染,最后由Display进行显示,这样的话,就能很好的保证我们的界面显示问题了,当然,任何软件措施都无法保证100%的解决问题,但是从现在实际场景来看效果,确实还是非常满意的。

     现在呢,我们就来了解一下Vsync垂直同步信号。从上面的逻辑图当中,应该也很容易看出来,我们需要的是什么呢?就是一个信号产生器就OK了,由它产生信号,指挥CPU、GPU、Display三者对UI进行同步的合成,这样就够了,那么Google在提供这个功能的时候,提供了两种方式:一种是由硬件产生,一种是由软件产生。接下来我们就来看一下Vsync的生产过程,产生的代码在HWComposer.cpp当中,composer的英文意思就是设计者,看着这个名字都让我们非常清晰它的作用,这也是细节之处,高手写出来的代码,处处都是亮点,小到一个变量、一个方法的修饰词,大到一个类、项目框架,里边有很多值得我们学习的地方。下面我们来看一下这块的代码:     

HWComposer::HWComposer(
        const sp& flinger,
        EventHandler& handler)
    : mFlinger(flinger),
      mFbDev(0), mHwc(0), mNumDisplays(1),
      mCBContext(new cb_context),
      mEventHandler(handler),
      mDebugForceFakeVSync(false)
{
    for (size_t i =0 ; i> 24) & 0xff,
              (hwcApiVersion(mHwc) >> 16) & 0xff);
        if (mHwc->registerProcs) {
            mCBContext->hwc = this;
            mCBContext->procs.invalidate = &hook_invalidate;
            mCBContext->procs.vsync = &hook_vsync;
            if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1))
                mCBContext->procs.hotplug = &hook_hotplug;
            else
                mCBContext->procs.hotplug = NULL;
            memset(mCBContext->procs.zero, 0, sizeof(mCBContext->procs.zero));
            mHwc->registerProcs(mHwc, &mCBContext->procs);
        }

        // don't need a vsync thread if we have a hardware composer
        needVSyncThread = false;
        // always turn vsync off when we start
        eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0);

        // the number of displays we actually have depends on the
        // hw composer version
        if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_3)) {
            // 1.3 adds support for virtual displays
            mNumDisplays = MAX_HWC_DISPLAYS;
        } else if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) {
            // 1.1 adds support for multiple displays
            mNumDisplays = NUM_BUILTIN_DISPLAYS;
        } else {
            mNumDisplays = 1;
        }
    }

    if (mFbDev) {
        ALOG_ASSERT(!(mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)),
                "should only have fbdev if no hwc or hwc is 1.0");

        DisplayData& disp(mDisplayData[HWC_DISPLAY_PRIMARY]);
        disp.connected = true;
        disp.width = mFbDev->width;
        disp.height = mFbDev->height;
        disp.format = mFbDev->format;
        disp.xdpi = mFbDev->xdpi;
        disp.ydpi = mFbDev->ydpi;
        if (disp.refresh == 0) {
            disp.refresh = nsecs_t(1e9 / mFbDev->fps);
            ALOGW("getting VSYNC period from fb HAL: %lld", disp.refresh);
        }
        if (disp.refresh == 0) {
            disp.refresh = nsecs_t(1e9 / 60.0);
            ALOGW("getting VSYNC period from thin air: %lld",
                    mDisplayData[HWC_DISPLAY_PRIMARY].refresh);
        }
    } else if (mHwc) {
        // here we're guaranteed to have at least HWC 1.1
        for (size_t i =0 ; i

     首先调用property_get获取系统属性,然后加载硬件模块loadHwcModule,mHwc变量的意思就很明显了,就是当前是否有硬件模块支持,如果有,那么needVSyncThread就赋值为false,表示Vsync信号由硬件产生,不需要软件模拟,否则的话,就需要由软件来产生了。那么如果需要由软件产生的话,就启动一个VSyncThread来执行这个功能。这个方法的意思也非常清晰,就是要确定好Vsync信号的产生源,到底这个事情由硬件来作,还是由软件来作。当然中间还包括一些初始化、设备加载等的逻辑。

     下面我们就分硬件产生和软件产生两个方面来分析一下后续流程:

     一、Vsync由硬件产生

     如果Vsync信号由硬件产生,那么我们在执行初始化的时候,需要注册硬件回调,也就是我们上面代码当中mHwc->registerProcs(mHwc, &mCBContext->procs)这句的作用,mHwc为true,mCBContext是HWComposer的私有类变量,是一个cb_context*指针,它的定义也在HWComposer.cpp当中,代码如下:

struct HWComposer::cb_context {
    struct callbacks : public hwc_procs_t {
        // these are here to facilitate the transition when adding
        // new callbacks (an implementation can check for NULL before
        // calling a new callback).
        void (*zero[4])(void);
    };
    callbacks procs;
    HWComposer* hwc;
};
     可以看到mCBContext->procs是一个callbacks的回调接口,后期当有事件产生时,如vsync或者invalidate,硬件模块将分别通过procs.vsync或者procs.invalidate来通知HWComposer。这也就是HWComposer.h头文件中定义的hook_invalidate、hook_vsync两个方法的作用,hook就是勾子的意思,在OpenGL的本地化窗口实现SurfaceFlinger、Surface的定义当中,大家也可以看到这样的方法命名,非常形象,就是和硬件勾在一处的。那么如果有Vsync事件到来,就会执行ctx->hwc->vsync(disp, timestamp),我们来看一下它的调用,代码如下:

void HWComposer::vsync(int disp, int64_t timestamp) {
    if (uint32_t(disp) < HWC_NUM_PHYSICAL_DISPLAY_TYPES) {
        {
            Mutex::Autolock _l(mLock);

            // There have been reports of HWCs that signal several vsync events
            // with the same timestamp when turning the display off and on. This
            // is a bug in the HWC implementation, but filter the extra events
            // out here so they don't cause havoc downstream.
            if (timestamp == mLastHwVSync[disp]) {
                ALOGW("Ignoring duplicate VSYNC event from HWC (t=%lld)",
                        timestamp);
                return;
            }

            mLastHwVSync[disp] = timestamp;
        }

        char tag[16];
        snprintf(tag, sizeof(tag), "HW_VSYNC_%1u", disp);
        ATRACE_INT(tag, ++mVSyncCounts[disp] & 1);

        mEventHandler.onVSyncReceived(disp, timestamp);
    }
}

     这里就是将产生的事件直接传递给mEventHandler,传递什么呢?其实就是一个时间点,我们回头看一下,解决Jank的方案就是要确定好Vsync信号产生的时间点,所以呢,这里由硬件确定好时间点,然后通知各个模块,当然,大家肯定都以系统时间为基点了,这样才能保证统一。我们来看一下mEventHandler是谁创建的。这就联系到SurfaceFlinger的初始化过程了,HWComposer就是在那里创建的,代码如下:

void SurfaceFlinger::init() {
    ALOGI(  "SurfaceFlinger's main thread ready to run. "
            "Initializing graphics H/W...");

    status_t err;
    Mutex::Autolock _l(mStateLock);

    // initialize EGL for the default display
    mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(mEGLDisplay, NULL, NULL);

    // Initialize the H/W composer object.  There may or may not be an
    // actual hardware composer underneath.
    mHwc = new HWComposer(this,
            *static_cast(this));

    // get a RenderEngine for the given display / config (can't fail)
    mRenderEngine = RenderEngine::create(mEGLDisplay, mHwc->getVisualID());

    // retrieve the EGL context that was selected/created
    mEGLContext = mRenderEngine->getEGLContext();

    LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT,
            "couldn't create EGLContext");

    // initialize our non-virtual displays
    for (size_t i=0 ; iisConnected(i) || type==DisplayDevice::DISPLAY_PRIMARY) {
            // All non-virtual displays are currently considered secure.
            bool isSecure = true;
            createBuiltinDisplayLocked(type);
            wp token = mBuiltinDisplays[i];

            sp bq = new BufferQueue(new GraphicBufferAlloc());
            sp fbs = new FramebufferSurface(*mHwc, i, bq);
            int32_t hwcId = allocateHwcDisplayId(type);
            sp hw = new DisplayDevice(this,
                    type, hwcId, mHwc->getFormat(hwcId), isSecure, token,
                    fbs, bq,
                    mRenderEngine->getEGLConfig());
            if (i > DisplayDevice::DISPLAY_PRIMARY) {
                // FIXME: currently we don't get blank/unblank requests
                // for displays other than the main display, so we always
                // assume a connected display is unblanked.
                ALOGD("marking display %d as acquired/unblanked", i);
                hw->acquireScreen();
            }
            mDisplays.add(token, hw);
        }
    }

    // make the GLContext current so that we can create textures when creating Layers
    // (which may happens before we render something)
    getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);

    // start the EventThread
    sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            vsyncPhaseOffsetNs, true);
    mEventThread = new EventThread(vsyncSrc);
    sp sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            sfVsyncPhaseOffsetNs, false);
    mSFEventThread = new EventThread(sfVsyncSrc);
    mEventQueue.setEventThread(mSFEventThread);

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

    // set a fake vsync period if there is no HWComposer
    if (mHwc->initCheck() != NO_ERROR) {
        mPrimaryDispSync.setPeriod(16666667);
    }

    // initialize our drawing state
    mDrawingState = mCurrentState;

    // set initial conditions (e.g. unblank default device)
    initializeDisplays();

    // start boot animation
    startBootAnim();
}
 
     可以看到,在这里创建HWComposer的时候,第二个参数就是要赋值给HWComposer的构造函数中的handler,而且传入的是一个this指针,那就是说当前的SurfaceFlinger就是那个回调的处理类,我们找一下SurfaceFlinger的定义,在SurfaceFlinger.h中,因为定义太长,这里就不引用代码了,可以明显看到SurfaceFlinger就是继承HWComposer::EventHandler,所以它可以在接收到HWComposer传过来的事件,并进行处理。好了,到这里呢,硬件的产生流程我们就清楚了,至于SurfaceFlinger的具体处理,我们放在下一篇博客中进行讲解。
     二、Vsync由软件产生
     软件源和硬件源相比最大的一个区别就是它需要启动一个新的线程VSyncThread,它的运行优先级和SurfaceFlinger是一样的,都是-9,我们先来看一下VSyncThread的定义,代码在HWComposer.h当中:
    class VSyncThread : public Thread {
        HWComposer& mHwc;
        mutable Mutex mLock;
        Condition mCondition;
        bool mEnabled;
        mutable nsecs_t mNextFakeVSync;
        nsecs_t mRefreshPeriod;
        virtual void onFirstRef();
        virtual bool threadLoop();
    public:
        VSyncThread(HWComposer& hwc);
        void setEnabled(bool enabled);
    };
     可以看到,它是继承Thread的,那么当它启动起来之后,就会执行threadLoop方法去不断的根据时间点产生Vsync信号。在源码当中,可以看到以loop命名的方法是非常多的,就像一个循环一样,不断在执行事件,比如我们应用程序当的主线程,也是一个Looper.loop(),这样才能保证我们的应用程序能响应到所有的事件:包括用户触发的、系统分发的、UI显示的等等。来看一下threadLoop方法是如何通过软件源产生Vsync信号的:
bool HWComposer::VSyncThread::threadLoop() {
    { // scope for lock
        Mutex::Autolock _l(mLock);
        while (!mEnabled) {
            mCondition.wait(mLock);
        }
    }

    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;

    struct timespec spec;
    spec.tv_sec  = next_vsync / 1000000000;
    spec.tv_nsec = next_vsync % 1000000000;

    int err;
    do {
        err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
    } while (err<0 && errno == EINTR);

    if (err == 0) {
        mHwc.mEventHandler.onVSyncReceived(0, next_vsync);
    }

    return true;
}
     mEnabled是一个Vsync的信号开关,可以通过它判断系统是否使能了Vsync的软件触发机制,接下来就需要计算Vsync的信号时间点了,mRefreshPeriod是指两个信号之间的时间间隔,它是在VSyncThread启动时,由hwc.getRefreshPeriod(HWC_DISPLAY_PRIMARY)方法进行赋值的,getRefreshPeriod方法实现很简单,就是获取当前mDisplayData[disp].refresh,也就是当前显示器的刷新间隔时间,disp是指第几个显示器,因为Android系统的定义是支持多个显示器的,不过当前的实际使用当中只用到了一个,所以disp的值一般都是0,这也是和Linux当中的显示驱动相对应的,在设备文件系统中对应是就是fb0的设备节点。接下来获取当前时间点,然后是产生信号的时间,得到了产生信号的时间和当前时间,紧接着就可以计算出中间要休眠多久了,如果休眠时间小于0,说明上一个Vsync信号已经被错过了,那么就要重新计算下一次的Vsync的产生时间,那么如何在指定的时间点产生信号呢?有两种方法,第一种方法是采用定时回调,第二种是采用休眠的形式主动等待,从代码中可以看到,HWComposer使用的是后者。一般的显示器的刷新频率是60Hz,也就是说每秒会刷新60次,那么每次所需要的时间大概就是16ms,可以想像,这里对时间的要求是非常高的,所以系统采用更高级别的nanosecond,也就是纳秒,clock_nanosleep传入的第一个参数是CLOCK_MONOTONIC,这种类型的时钟更加稳定,且不受系统时间的影响。 最后呢,调用 clock_nanosleep进入休眠,当休眠时间到了,就通过 mHwc.mEventHandler.onVSyncReceived(0, next_vsync)通知事件的关心者,信号到了,你们需要起床干活了。
     这里有些奇怪了,应该是一个循环才对啊,比如我们在应用主线程中也是通过一个for(::)无限循环来不断的处理事件的,这里为什么只有一个do/while,那么执行一次之后,while循环跳出了,分发一次,后面就不再分发了?这显然是不可能的,在这里的无限循环是通过返回值true来控制的,当threadLoop返回true时,它将会被系统调次调用,也就形成循环了。
     好了,这篇博客,我们就讲到这里了,至于课题当中的SurfaceFlinger对Vsync的事件响应及处理,我们在下一篇当中进行讨论!
     同学们,下课!!

你可能感兴趣的:(android,framework,Android,View视图,Android框架总结)