
VSYNC 的概念
VSYNC(Vertical Synchronization)是一个相当古老的概念,对于游戏玩家,它有一个更加大名鼎鼎的中文名字—-垂直同步。
“垂直同步(vsync)”指的是显卡的输出帧数和屏幕的垂直刷新率相同,这完全是一个CRT显示器上的概念。其实无论是VSYNC还是垂直同步这个名字,因为LCD根本就没有垂直扫描的这种东西,因此这个名字本身已经没有意义。但是基于历史的原因,这个名称在图形图像领域被沿袭下来。
在当下,垂直同步的含义我们可以理解为,使得显卡生成帧的速度和屏幕刷新的速度的保持一致。举例来说,如果屏幕的刷新率为60Hz,那么生成帧的速度就应该被固定在1/60 s。
Android中的VSYNC — 黄油计划
从Android 4.1开始,谷歌致力于解决Android系统中最饱受诟病的一个问题,滑动不如iOS流畅。因谷歌在4.1版本引入了一个重大的改进—Project Butter,也即是黄油计划。
Project Butter对Android Display系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer。关于后面两个概念我们会在后面开专题讲解,这里我们重点讲解VSYNC的作用。
玩过大型PC游戏的玩家都知道,VSYNC最重要的作用是防止出现画面撕裂(screentearing)。所谓画面撕裂,就是指一个画面上出现了两帧画面的内容,如下图。
为什么会出现这种情况呢?这种情况一般是因为显卡输出帧的速度高于显示器的刷新速度,导致显示器并不能及时处理输出的帧,而最终出现了多个帧的画面都留在了显示器上的问题。这也就是我们所说的画面撕裂。
提到垂直同步这里就多提一句,其实我认为对于PC上的大型游戏来说,只有配置足够高,高到显卡输出帧率可以稳定的高于显示器的刷新频率,才有开启垂直同步的必要。因为只有这个时候,画面撕裂才会真正成为一个问题。而对于很多情况下主机性能不足导致游戏输出帧率低于显示器的刷新频率的情况下,尤其是帧率稳定在40~60之间时,开启垂直同步可能会导致帧率倍数级的下降(具体原因我们在Graphic架构一文中提到过,当帧生成速度不及VSync速度时,帧率的下降不是平缓的,而且很可能是倍数级的。当然这在android系统上并非严重问题,因为android上很少有高速的复杂场景的频繁切换。事实上,在Android的普通应用场景下,VSync的使用不仅不会降低帧率,还可以有效解决卡顿问题)。
回到正文中来,那么VSync除了可以解决画面的撕裂的问题,还可以解决别的什么问题吗?我们来看下图:
这个图中有三个元素,Display是显示屏幕,GPU和CPU负责渲染帧数据,每个帧以方框表示,并以数字进行编号,如0、1、2等等。VSync用于指导双缓冲区的交换。
以时间的顺序来看下将会发生的异常:
Step1. Display显示第0帧数据,此时CPU和GPU渲染第1帧画面,而且赶在Display显示下一帧前完成
Step2. 因为渲染及时,Display在第0帧显示完成后,也就是第1个VSync后,正常显示第1帧
Step3. 由于某些原因,比如CPU资源被占用,系统没有及时地开始处理第2帧,直到第2个VSync快来前才开始处理
Step4. 第2个VSync来时,由于第2帧数据还没有准备就绪,显示的还是第1帧。这种情况被Android开发组命名为“Jank”。
Step5. 当第2帧数据准备完成后,它并不会马上被显示,而是要等待下一个VSync。
所以总的来说,就是屏幕平白无故地多显示了一次第1帧。原因大家应该都看到了,就是CPU没有及时地开始着手处理第2帧的渲染工作,以致“延误军机”。
其实总结上面的这个情况之所以发生,首先的原因就在于第二帧没有及时的绘制(当然即使第二帧及时绘制,也依然可能出现Jank,这就是同时引入三重缓冲的作用。我们将在三重缓冲一节中再讲解这种情况)。那么如何使得第二帧即使被绘制呢?
这就是我们在Graphic系统中引入VSYNC的原因,考虑下面这张图:
如上图所示,一旦VSync出现后,立刻就开始执行下一帧的绘制工作。这样就可以大大降低Jank出现的概率。另外,VSYNC引入后,要求绘制也只能在收到VSYNC消息之后才能进行,因此,也就杜绝了另外一种极端情况的出现—-CPU(GPU)一直不停的进行绘制,帧的生成速度高于屏幕的刷新速度,导致生成的帧不能被显示,只能丢弃,这样就出现了丢帧的情况—-引入VSYNC后,绘制的速度就和屏幕刷新的速度保持一致了。
VSYNC信号的生成
那么VSYNC信号是如何生成的呢?
Android系统中VSYNC信号分为两种,一种是硬件生成的信号,一种是软件模拟的信号。
硬件信号是由HardwareComposer提供的,HWC封装了相关的HAL层,如果硬件厂商提供的HAL层实现能定时产生VSYNC中断,则直接使用硬件的VSYNC中断,否则HardwareComposer内部会通过VSyncThread来模拟产生VSYNC中断(其实现很简单,就是sleep固定时间,然后唤醒)。
回到我们上一节中讲到的SurfaceFlinger的启动过程inti函数上来,上一节我们提到init函数内会创建一个HWComposer对象。
- HWComposer::HWComposer(
- const sp<SurfaceFlinger>& flinger,
- EventHandler& handler)
- : mFlinger(flinger),
- mFbDev(0), mHwc(0), mNumDisplays(1),
- mCBContext(new cb_context),
- mEventHandler(handler),
- mDebugForceFakeVSync(false)
- {
- …
- //首先是一些和VSYNC有关的信息的初始化
- //因为在硬件支持的情况下,VSYNC的功能就是由HWC提供的
- for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) {
- mLastHwVSync[i] = 0;
- mVSyncCounts[i] = 0;
- }
- //根据配置来看是否需要模拟VSYNC消息
- char value[PROPERTY_VALUE_MAX];
- property_get(“debug.sf.no_hw_vsync”, value, “0”);
- mDebugForceFakeVSync = atoi(value);
- …
- // 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);
- //显然,如果需要模拟VSync信号的话,我们需要线程来做这个工作
- if (needVSyncThread) {
- // we don’t have VSYNC support, we need to fake it
- //VSyncThread类的实现很简单,无非就是一个计时器而已,定时发送消息而已
- //TODO VSYNC专题
- mVSyncThread = new VSyncThread(*this);
- }
- …
- }
- HWComposer::HWComposer(
- const sp<SurfaceFlinger>& flinger,
- EventHandler& handler)
- : mFlinger(flinger),
- mFbDev(0), mHwc(0), mNumDisplays(1),
- mCBContext(new cb_context),
- mEventHandler(handler),
- mDebugForceFakeVSync(false)
- {
- …
- //首先是一些和VSYNC有关的信息的初始化
- //因为在硬件支持的情况下,VSYNC的功能就是由HWC提供的
- for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) {
- mLastHwVSync[i] = 0;
- mVSyncCounts[i] = 0;
- }
- //根据配置来看是否需要模拟VSYNC消息
- char value[PROPERTY_VALUE_MAX];
- property_get(“debug.sf.no_hw_vsync”, value, “0”);
- mDebugForceFakeVSync = atoi(value);
- …
- // 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);
- //显然,如果需要模拟VSync信号的话,我们需要线程来做这个工作
- if (needVSyncThread) {
- // we don’t have VSYNC support, we need to fake it
- //VSyncThread类的实现很简单,无非就是一个计时器而已,定时发送消息而已
- //TODO VSYNC专题
- mVSyncThread = new VSyncThread(*this);
- }
- …
- }
我们来看下上面这段代码。
首先mDebugForceFakeVSync是为了调制,可以通过这个变量设置强制使用软件VSYNC模拟。
然后针对不同的屏幕,初始化了他们的mLastHwVSync和mVSyncCounts值。
如果硬件支持,那么就把needVSyncThread设置为false,表示不需要软件模拟。
接着通过eventControl来暂时的关闭了VSYNC信号,这一点将在下面讲解eventControl时一并讲解。
最后,如果需要软件模拟Vsync信号的话,那么我们将通过一个单独的VSyncThread线程来做这个工作(fake VSYNC是这个线程唯一的作用)。我们来看下这个线程。
软件模拟
- bool HWComposer::VSyncThread::threadLoop() {
- const nsecs_t period = mRefreshPeriod;
- //当前的时间
- const nsecs_t now = systemTime(CLOCK_MONOTONIC);
- //下一次VSYNC到来的时间
- nsecs_t next_vsync = mNextFakeVSync;
- //为了等待下个时间到来应该休眠的时间
- nsecs_t sleep = next_vsync – now;
- //错过了VSYNC的时间
- if (sleep < 0) {
- // we missed, find where the next vsync should be
- //重新计算下应该休息的时间
- sleep = (period – ((now – next_vsync) % period));
- //更新下次VSYNC的时间
- next_vsync = now + sleep;
- }
- //更新下下次VSYNC的时间
- 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) {
- //休眠之后,到了该发生VSYNC的时间了
- mHwc.mEventHandler.onVSyncReceived(0, next_vsync);
- }
- return true;
- }
这个函数其实很简单,无非就是一个简单的时间计算,计算过程我已经写在了程序注释里面。总之到了应该发生VSYNC信号的时候,就调用了mHwc.mEventHandler.onVSyncReceived(0, next_vsync)函数来通知VSYNC的到来。
我们注意到mEventHandler实际上是在HWC创建时被传入的,我们来看下HWC创建时的代码.
- mHwc = new HWComposer(this,
- *static_cast<HWComposer::EventHandler *>(this));
- class SurfaceFlinger : public BnSurfaceComposer,
- private IBinder::DeathRecipient,
- private HWComposer::EventHandler
可以看到这个mEventHandler实际上就是SurfaceFlinger。也就是说,VSYNC信号到来时,SurfaceFlinger的onVSyncReceived函数处理了这个消息。
这里我们暂时先不展开SurfaceFlinger内的逻辑处理,等我们下面分析完硬件实现后,一并进行分析
硬件实现
上面我们讲了软件如何模拟一个VSYNC信号并通知SurfaceFlinger,那么硬件又是如何实现这一点的呢?
我们再一次回到HWC的创建过程中来:
- if (mHwc) {
- ALOGE(“Lee Using %s version %u.%u”, HWC_HARDWARE_COMPOSER,
- (hwcApiVersion(mHwc) >> 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);
- }
来看下上面这段实现。
当HWC有vsync信号生成时,硬件模块会通过procs.vsync来通知软件部分,因此也就是调用了hook_vsync函数。
- void HWComposer::hook_vsync(const struct hwc_procs* procs, int disp,
- int64_t timestamp) {
- cb_context* ctx = reinterpret_cast<cb_context*>(
- const_cast<hwc_procs_t*>(procs));
- ctx->hwc->vsync(disp, timestamp);
- }
- void HWComposer::vsync(int disp, int64_t timestamp) {
- //只有真实的硬件设备才会产生VSYNC
- if (uint32_t(disp) < HWC_NUM_PHYSICAL_DISPLAY_TYPES) {
- {
- mLastHwVSync[disp] = timestamp;
- }
- mEventHandler.onVSyncReceived(disp, timestamp);
- }
我们发现最后殊途同归,硬件信号最终也通过onVSyncReceived函数通知到了SurfaceFlinger了。下面我们来分析下SurfaceFlinger的处理过程。
Surfaceflinger对VSYNC消息的处理
在4.4之前,本来SurfaceFlinger对VSYNC的处理比较简单,只是通知EventThread进行处理即可,但是KK再次对VSYNC这一段的逻辑进一步的细化和复杂化。不得不说,Google为了提升Android UI的流畅性真是费尽心思。
虽然只是一个简单的信号,但是KK的处理已经相当复杂。先来直接看下Surfaceflinger的onVSyncReceived函数:
- void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
- bool needsHwVsync = false;
- {
- if (type == 0 && mPrimaryHWVsyncEnabled) {
- needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
- }
- }
- if (needsHwVsync) {
- enableHardwareVsync();
- } else {
- disableHardwareVsync(false);
- }
- }
这段代码很短,但是相当让人困惑,困惑至少有以下三点:
- mPrimaryHWVsyncEnabled是干嘛的,是被什么时候赋值的?
- enableHardwareVsync和disableHardwareVsync又起到了什么作用?
- mPrimaryDispSync是什么?addResyncSample有什么作用?
Surfaceflinger的init函数
要回答这三个问题,我们首先还是得回到SurfaceFlinger的init函数中来。
EventThread的创建
上一节中SF的创建过程中我们提到了EventThread的创建。
- {
- …
- // start the EventThread
- sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
- vsyncPhaseOffsetNs, true);
- mEventThread = new EventThread(vsyncSrc);
- sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
- sfVsyncPhaseOffsetNs, true);
- 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);
- }
- …
- }
这里看起来更奇怪了。首先这里有两个看起来几乎一样的DispSyncSource,看起来唯一的区别就是一个偏移时间的不同。
而其实,这两个DispSyncSource就是KK引入的重大变化。Android 4.4(KitKat)引入了VSync的虚拟化,即把硬件的VSync信号先同步到一个本地VSync模型中,再从中一分为二,引出两条VSync时间与之有固定偏移的线程。示意图如下:
而Google这样修改的目的又是什么呢?
回忆一下我们在Graphic架构一文中指出的,在当前三重缓冲区的架构下,即对于一帧内容,先等App UI画完了,SurfaceFlinger再出场对其进行合并渲染后放入framebuffer,最后整到屏幕上。而现有的VSync模型是让大家一起开始干活。
这个架构其实会产生一个问题,因为App和SurfaceFlinger被同时唤醒,导致他们二者总是一起工作,必然导致VSync来临的时刻,这二者之间产生了CPU资源的抢占。因此,谷歌给这两个工作都加上一个小小的延迟,让这两个工作并不是同时被唤醒,这样大家就可以错开使用资源的高峰期,提高工作的效率。
这两个延迟,其实就分别对应上面代码中的vsyncSrc(绘制延迟)和sfVsyncSrc(合成延迟)。
了解了这些背景知识后,我们来继续看代码,DispSyncSource的初始化如此简单,这里不再详述(当然这里的变量mPrimaryDispSync的值来源让人困惑)。
在创建了两个DispSyncSource变量后,我们使用它们来初始化了两个EventThread。下面我们来详细看下EventThread的创建流程:
- EventThread::EventThread(const sp<VSyncSource>& src)
- : mVSyncSource(src),
- mUseSoftwareVSync(false),
- mVsyncEnabled(false),
- mDebugVsyncEnabled(false) {
- for (int32_t i=0 ; i<DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES ; i++) {
- mVSyncEvent[i].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
- mVSyncEvent[i].header.id = 0;
- mVSyncEvent[i].header.timestamp = 0;
- mVSyncEvent[i].vsync.count = 0;
- }
- }
- void EventThread::onFirstRef() {
- run(“EventThread”, PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);
- }
EventThread的构造函数很简单。重点是它的onFirstRef函数启动了一个EventThread线程,于是下面的代码才是重点:
- bool EventThread::threadLoop() {
- DisplayEventReceiver::Event event;
- Vector< sp<EventThread::Connection> > signalConnections;
- signalConnections = waitForEvent(&event);
- // dispatch events to listeners…
- const size_t count = signalConnections.size();
- for (size_t i=0 ; i<count ; i++) {
- const sp<Connection>& conn(signalConnections[i]);
- // now see if we still need to report this event
- status_t err = conn->postEvent(event);
- 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.
- // FIXME: 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.
- ALOGW(“EventThread: dropping event (%08x) for connection %p”,
- event.header.type, conn.get());
- } 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.
- removeDisplayEventConnection(signalConnections[i]);
- }
- }
- return true;
- }
上面的函数本身并不复杂,其中调用了一个waitForEvent的函数。这个函数相当之长,为了防止代码展开太多,我们这里暂时不再详细分析这个函数。我们目前只需要知道这个函数的最重要的作用是等待Event的到来,并且查找对event感兴趣的监听者,而在没有event到来时,线程处于休眠状态,等待event的唤醒(我们将在下一篇文章VSYNC的接收和处理中展开分析这个函数)。
这样,EventThread线程就运行起来,处在等待被event唤醒的状态下。
MessageQueue和EventThread建立连接
简单说明完EventThread之后,我们再次回到SurfaceFlinger的init过程中来。回到刚才我们本小节开始我们分析的代码中来:
- {
- …
- // start the EventThread
- sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
- vsyncPhaseOffsetNs, true);
- mEventThread = new EventThread(vsyncSrc);
- sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
- sfVsyncPhaseOffsetNs, true);
- mSFEventThread = new EventThread(sfVsyncSrc);
- mEventQueue.setEventThread(mSFEventThread);
- …
- }
下面这句 mEventQueue.setEventThread(mSFEventThread);其实很重要,这个函数将SurfaceFlinger的MessageQueue真正和我们刚才创建的EventThread建立起了连接,这样SurfaceFlinger才能真正接收到来自HWC的VSYNC信号。
我们来看下这段代码:
- void MessageQueue::setEventThread(const sp<EventThread>& eventThread)
- {
- mEventThread = eventThread;
- mEvents = eventThread->createEventConnection();
- mEventTube = mEvents->getDataChannel();
- mLooper->addFd(mEventTube->getFd(), 0, ALOOPER_EVENT_INPUT,
- MessageQueue::cb_eventReceiver, this);
- }
这里代码逻辑其实很简单,就是创建了一个到EventThread的连接,得到了发送VSYNC事件通知的BitTube,然后监控这个BitTube中的套接字,并且指定了收到通知后的回调函数,MessageQueue::cb_eventReceiver。这样一旦VSync信号传来,函数cb_eventReceiver将被调用。
上面这个过程的原理,应该是使用了linux中的socket通信原理,因为和本文主题关系不大,不再展开。而MessageQueue在收到VSync消息后做了什么,我们将在下一篇文章VSync的接收和处理中详细说明。
EventControlThread
回到SurfaceFlinger的init过程中来,在EventThread创建好,MessageQueue和EventThread建立连接之后,SurfaceFlinger又创建了一个EventControlThread。
- {
- …
- 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);
- }
- …
- }
我们来看下这个EventControlThread是做什么的:
- EventControlThread::EventControlThread(const sp<SurfaceFlinger>& flinger):
- mFlinger(flinger),
- mVsyncEnabled(false) {
- }
- bool EventControlThread::threadLoop() {
- Mutex::Autolock lock(mMutex);
- bool vsyncEnabled = mVsyncEnabled;
- mFlinger->eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC,
- mVsyncEnabled);
- while (true) {
- status_t err = mCond.wait(mMutex);
- if (err != NO_ERROR) {
- ALOGE(“error waiting for new events: %s (%d)”,
- strerror(-err), err);
- return false;
- }
- if (vsyncEnabled != mVsyncEnabled) {
- mFlinger->eventControl(HWC_DISPLAY_PRIMARY,
- SurfaceFlinger::EVENT_VSYNC, mVsyncEnabled);
- vsyncEnabled = mVsyncEnabled;
- }
- }
- return false;
- }
- void SurfaceFlinger::eventControl(int disp, int event, int enabled) {
- getHwComposer().eventControl(disp, event, enabled);
- }
其实EventControlThread的功能很简单,从名字里面我们就可以看出来,这个线程最重要的作用就是给HWC硬件发送消息,来通知开启或者关闭VSync消息的。
从代码来看,线程开始时,根据当前状态来通知硬件是否要发送VSync信号(刚开始时应该是关闭的)。然后进入等待:mCond.wait(mMutex),等待的信号来源也很简单:
- void EventControlThread::setVsyncEnabled(bool enabled) {
- Mutex::Autolock lock(mMutex);
- mVsyncEnabled = enabled;
- mCond.signal();
- }
也就是说,一旦有人调用了EventControlThread的setVsyncEnabled函数时,将发送通知,然后条件被触发,EventControlThread根据当前最新的状态来重新给硬件发送命令,来告知硬件是否要开启VSync信号。那么是谁在什么时候调用了setVsyncEnabled函数呢?我们这里先不跟进,等到下一小节我们讲完屏幕的点亮和熄灭后大家就会明白这一段。
onScreenAcquired
Surfaceflinger的init函数中,除了和EventThread相关的代码外,还有一段显示设备的初始化:
- {
- …
- // set initial conditions (e.g. unblank default device)
- initializeDisplays();
- …
- }
我们在上一篇文档SF的创建过程中已经讲解过,其中initializeDisplays函数调用了一个重要的函数onScreenAcquired,前面文档中我们也说过,这个函数不仅在这里会被调用,同样会在屏幕点亮时被调用。
- void SurfaceFlinger::onScreenAcquired(const sp<const DisplayDevice>& hw) {
- …
- if (type < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES) {
- // built-in display, tell the HWC
- getHwComposer().acquire(type);
- if (type == DisplayDevice::DISPLAY_PRIMARY) {
- // FIXME: eventthread only knows about the main display right now
- mEventThread->onScreenAcquired();
- resyncToHardwareVsync(true);
- }
- }
- …
- }
首先我们来看下EventThread类的onScreenAcquired函数:
- void EventThread::onScreenAcquired() {
- Mutex::Autolock _l(mLock);
- if (mUseSoftwareVSync) {
- // resume use of h/w vsync
- mUseSoftwareVSync = false;
- mCondition.broadcast();
- }
- }
记得我们前面在讲EventThread时提到的,EventThread开始threadLoop以后,会在waitForEvent中等待被通知,而这个被等待的通知。就是在这个onScreenAcquired函数中发出的通知,mCondition.broadcast()(当VSYNC信号到达时,也会发出这个通知)。
继续看下resyncToHardwareVsync函数:
- void SurfaceFlinger::resyncToHardwareVsync(bool makeAvailable) {
- if (makeAvailable) {
- mHWVsyncAvailable = true;
- } else if (!mHWVsyncAvailable) {
- ALOGE(“resyncToHardwareVsync called when HW vsync unavailable”);
- return;
- }
- const nsecs_t period =
- getHwComposer().getRefreshPeriod(HWC_DISPLAY_PRIMARY);
- mPrimaryDispSync.reset();
- mPrimaryDispSync.setPeriod(period);
- if (!mPrimaryHWVsyncEnabled) {
- mPrimaryDispSync.beginResync();
- mEventControlThread->setVsyncEnabled(true);
- mPrimaryHWVsyncEnabled = true;
- }
- }
这个函数是一个比较重要的函数。主要的功能就是打开HWC的VSync功能。
首先是获取HWC的信号发送间隔,设置给mPrimaryDispSync,然后重置mPrimaryDispSync。
如果之前没有使用HWC的VSYNC,那么还需要重新开始mPrimaryDispSync的计数,然后调用EventControlThread的setVsyncEnabled函数,我们前面已经讲过这个函数的作用——就是打开硬件的VSYNC功能—-我们前面提出了什么时候调用setVsyncEnabled函数疑问,现在这个疑问可以被回答了,这个函数是在屏幕点亮时被调用了。
init中VSync的简单小节
这样我们简单的学习完了init中和VSync有关的一些知识。其实主要的流程代码就是下面的一小段:
- {
- …
- // start the EventThread
- sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
- vsyncPhaseOffsetNs, true);
- mEventThread = new EventThread(vsyncSrc);
- sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
- sfVsyncPhaseOffsetNs, true);
- 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();
- …
- }
- 首先就是创建了两个DispSyncSource和EventThread,这两组线程可以各自配置延迟,分别对应渲染和合成。这样渲染和合成就可以在收到真正的VSync信号之后错开执行。EventThread创建之后将会等待VSync信号的通知,一旦收到信号后将会分发给感兴趣的监听者。
- mEventQueue(MessageQueue)和EventThread建立连接,实际上就是注册成为了一个监听者。
- 创建了一个EventControlThread线程,用以和硬件HWC通信,控制HWC是否生成VSync信号。
- 最后mPrimaryDispSync.setPeriod(16666667)是指如果硬件不能生成VSync信号,那么软件将模拟生成,并且生成的周期设置为16.6ms左右。
5.最后在调用initializeDisplays函数初始化显示的过程中,我们又调用了onScreenAcquired函数,这个函数主要是唤醒了EventThread的等待,重置了mPrimaryDispSync,并且设置mPrimaryHWVsyncEnabled为true,并且通知硬件打开VSYNC通知。
基本流程如下图所示:
其中红色部分我们会在下一章VSync的接收和处理中再次展开讲解。
onVSyncReceived函数
这样,经过漫长的讲解,我们又从VSync的角度重新分析了SurfaceFlinger的init函数中的一些细节,现在我们终于可以回到这一小节开始之前我们提出的那几个问题:
当Surfaceflinger的收到VSync消息时的onVSyncReceived函数:
- void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
- bool needsHwVsync = false;
- {
- if (type == 0 && mPrimaryHWVsyncEnabled) {
- needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
- }
- }
- if (needsHwVsync) {
- enableHardwareVsync();
- } else {
- disableHardwareVsync(false);
- }
- }
这段代码很短,但是相当让人困惑,困惑至少有以下三点:
- mPrimaryHWVsyncEnabled是干嘛的,是被什么时候赋值的?
- enableHardwareVsync和disableHardwareVsync又起到了什么作用?
- mPrimaryDispSync是什么?addResyncSample有什么作用?
现在我们可以尝试来回答上面的这三个疑问了:
- mPrimaryHWVsyncEnabled是用来标识主屏幕对应的HWC的VSYNC功能有没有被开启。这个值在SurfaceFlinger创建时被赋值为false,在onScreenAcquired屏幕被点亮时,被设置为true。
- enableHardwareVsync所起的作用其实跟我们前面提到的resyncToHardwareVsync函数作用基本一样,也是打开硬件的VSync功能。而disableHardwareVsync自然就是关闭硬件的VSync功能。
- void SurfaceFlinger::enableHardwareVsync() {
- if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) {
- mPrimaryDispSync.beginResync();
- mEventControlThread->setVsyncEnabled(true);
- mPrimaryHWVsyncEnabled = true;
- }
- }
- mPrimaryDispSync的类型是DispSync(当然这个变量的创建时刻依然有待确认),表示了一个基于硬件VSync信号的同步模型,它会根据从HWComposer来的硬件VSync信号的采样来进行同步。
了解了上面三个问题之后,我们终于可以来看下这段代码中最重要的一个函数调用—-addResyncSample。
我们将在下一章的VSync消息的处理一章中详细分析这个函数以及后续消息的处理流程。
总结起来如下图:图中红色部分涉及到了VSync信号的在系统内的传输过程,我们在这一章详细的展开说明。
向Eventhread注册一个事件的监听者——createEventConnection
在SurfaceFlinger的init函数中,我们调用了mEventQueue.setEventThread(mSFEventThread)函数,我们在前面一章中已经提到过,这个函数将SurfaceFlinger的MessageQueue真正和我们刚才创建的EventThread建立起了连接。我们来看下这段代码:
- void MessageQueue::setEventThread(const sp<EventThread>& eventThread)
- {
- mEventThread = eventThread;
- mEvents = eventThread->createEventConnection();
- mEventTube = mEvents->getDataChannel();
- mLooper->addFd(mEventTube->getFd(), 0, ALOOPER_EVENT_INPUT,
- MessageQueue::cb_eventReceiver, this);
- }
createEventConnection是将EventThread和MessageQueue建立连接的函数:
- 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())
- {
- }
- void EventThread::Connection::onFirstRef() {
- mEventThread->registerDisplayEventConnection(this);
- }
- status_t EventThread::registerDisplayEventConnection(
- const sp<EventThread::Connection>& connection) {
- mDisplayEventConnections.add(connection);
- mCondition.broadcast();
- }
我们看这个函数虽然传递了很多次,但是其实逻辑依然很简单易懂。其实无非是这个函数会导致一个Connection类的创建,而这个connection类会被保存在EventThread下的一个容器内。
通过createEventConnection这样一个简单的方法,我们其实就注册了一个事件的监听者,得到了发送VSYNC事件通知的BitTube,然后监控这个BitTube中的套接字,并且指定了收到通知后的回调函数,MessageQueue::cb_eventReceiver。这样一旦VSync信号传来,函数cb_eventReceiver将被调用。
等待事件
继续回到上一章的图中:
刚才我们分析了创建一个连接,SurfaceFlinger的MessageQueue注册了一个监听者到EventThread中的mDisplayEventConnections容器中,等待event事件到来时被通知。下面我们继续分析一下我们前面一章没有展开说明的另外一个函数:
EventThread中等待事件时的函数waitForEvent。
- // This will return when (1) a vsync event has been received, and (2) there was
- // at least one connection interested in receiving it when we started waiting.
- Vector< sp<EventThread::Connection> > EventThread::waitForEvent(
- DisplayEventReceiver::Event* event)
- {
- // Here we figure out if we need to enable or disable vsyncs
- if (timestamp && !waitForVSync) {
- // we received a VSYNC but we have no clients
- // don’t report it, and disable VSYNC events
- disableVSyncLocked();
- } else if (!timestamp && waitForVSync) {
- // we have at least one client, so we want vsync enabled
- // (TODO: this function is called right after we finish
- // notifying clients of a vsync, so this call will be made
- // at the vsync rate, e.g. 60fps. If we can accurately
- // track the current state we could avoid making this call
- // so often.)
- enableVSyncLocked();
- }
- // note: !timestamp implies signalConnections.isEmpty(), because we
- // don’t populate signalConnections if there’s no vsync pending
- if (!timestamp && !eventPending) {
- // wait for something to happen
- if (waitForVSync) {
- // This is where we spend most of our time, waiting
- // for vsync events and new client registrations.
- //
- // If the screen is off, we can’t use h/w vsync, so we
- // use a 16ms timeout instead. It doesn’t need to be
- // precise, we just need to keep feeding our clients.
- //
- // We don’t want to stall if there’s a driver bug, so we
- // use a (long) timeout when waiting for h/w vsync, and
- // generate fake events when necessary.
- bool softwareSync = mUseSoftwareVSync;
- nsecs_t timeout = softwareSync ? ms2ns(16) : ms2ns(1000);
- if (mCondition.waitRelative(mLock, timeout) == TIMED_OUT) {
- if (!softwareSync) {
- ALOGW(“Timed out waiting for hw vsync; faking it”);
- }
- // FIXME: how do we decide which display id the fake
- // vsync came from ?
- mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
- mVSyncEvent[0].header.id = DisplayDevice::DISPLAY_PRIMARY;
- mVSyncEvent[0].header.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
- mVSyncEvent[0].vsync.count++;
- }
- } else {
- // Nobody is interested in vsync, so we just want to sleep.
- // h/w vsync should be disabled, so this will wait until we
- // get a new connection, or an existing connection becomes
- // interested in receiving vsync again.
- mCondition.wait(mLock);
- }
- }
- } while (signalConnections.isEmpty());
- // here we’re guaranteed to have a timestamp and some connections to signal
- // (The connections might have dropped out of mDisplayEventConnections
- // while we were asleep, but we’ll still have strong references to them.)
- return signalConnections;
- }
waitForEvent是一个相当长的函数,我们上文只截取了函数的后半部分,在被删掉的前半部分中,函数主要在处理和EventThread建立起连接的客户端,也就是我们上面提到的通过createEventConnection来于EventThread建立起连接的客户端。
前半部分代码通过计算对两个变量的赋值产生了影响,timestamp非零代表有一个VSync信号需要处理,waitForVSync为true则代表有客户端在等待VSync信号。我们下面来仔细分析下后半部分的代码:
- // Here we figure out if we need to enable or disable vsyncs
- if (timestamp && !waitForVSync) {
- // we received a VSYNC but we have no clients
- // don’t report it, and disable VSYNC events
- disableVSyncLocked();
- } else if (!timestamp && waitForVSync) {
- // we have at least one client, so we want vsync enabled
- // (TODO: this function is called right after we finish
- // notifying clients of a vsync, so this call will be made
- // at the vsync rate, e.g. 60fps. If we can accurately
- // track the current state we could avoid making this call
- // so often.)
- enableVSyncLocked();
- }
上面这段代码的含义其实注释里面写的很清楚,根据是否需要来决定禁用和开启VSync信号。两个分支的条件也都有注释,第一个分支的含义是收到了VSync但是却没有客户端感兴趣,这个时候我们会选择禁用VSync,另一条分支则是没有收到VSync信号但是有客户端对信号感兴趣,那么这个时候我们会开启VSync。
在上一章中我们已经提到了,真正向硬件发送开关VSync信号的命令的是EventControlThread,那么这里的disableVSyncLocked和enableVSyncLocked的原理是否和EventControlThread一致呢?我们来仔细分析一下这个函数:
- void EventThread::enableVSyncLocked() {
- if (!mUseSoftwareVSync) {//这个条件会筛掉灭屏的情况
- // never enable h/w VSYNC when screen is off
- if (!mVsyncEnabled) {
- mVsyncEnabled = true;
- mVSyncSource->setCallback(static_cast<VSyncSource::Callback*>(this));
- mVSyncSource->setVSyncEnabled(true);
- mPowerHAL.vsyncHint(true);
- }
- }
- mDebugVsyncEnabled = true;
- }
上面函数主要调用了VSyncSource的setVSyncEnabled函数:
- virtual void setVSyncEnabled(bool enable) {
- // Do NOT lock the mutex here so as to avoid any mutex ordering issues
- // with locking it in the onDispSyncEvent callback.
- if (enable) {
- status_t err = mDispSync->addEventListener(mPhaseOffset,
- static_cast<DispSync::Callback*>(this));
- if (err != NO_ERROR) {
- ALOGE(“error registering vsync callback: %s (%d)”,
- strerror(-err), err);
- }
- ATRACE_INT(“VsyncOn”, 1);
- } else {
- status_t err = mDispSync->removeEventListener(
- static_cast<DispSync::Callback*>(this));
- if (err != NO_ERROR) {
- ALOGE(“error unregistering vsync callback: %s (%d)”,
- strerror(-err), err);
- }
- ATRACE_INT(“VsyncOn”, 0);
- }
- }
- status_t DispSync::addEventListener(nsecs_t phase,
- const sp<Callback>& callback) {
- return mThread->addEventListener(phase, callback);
- }
看到这里我们发现,其实所谓的disableVSyncLocked和enableVSyncLocked禁用开启VSync信号并非真的让硬件停止发送信号,只是向DispSync(里面的DispSyncThread,DispSyncThread在DispSync被创建时启动)删除和添加事件的监听者,通过添加监听者,新的监听者就可以收到来自硬件的VSync消息,而一旦删除,EventThread不再能收到消息,显然对于EventThread来说,也就禁用了VSync消息。
分析完EventThread的disableVSyncLocked和enableVSyncLocked函数,我们回到waitForEvent函数继续分析。剩下的逻辑很简单了:
- // wait for something to happen
- if (waitForVSync) {
- // This is where we spend most of our time, waiting
- // for vsync events and new client registrations.
- //
- // If the screen is off, we can’t use h/w vsync, so we
- // use a 16ms timeout instead. It doesn’t need to be
- // precise, we just need to keep feeding our clients.
- //
- // We don’t want to stall if there’s a driver bug, so we
- // use a (long) timeout when waiting for h/w vsync, and
- // generate fake events when necessary.
- bool softwareSync = mUseSoftwareVSync;
- nsecs_t timeout = softwareSync ? ms2ns(16) : ms2ns(1000);
- if (mCondition.waitRelative(mLock, timeout) == TIMED_OUT) {
- if (!softwareSync) {
- ALOGW(“Timed out waiting for hw vsync; faking it”);
- }
- // FIXME: how do we decide which display id the fake
- // vsync came from ?
- mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
- mVSyncEvent[0].header.id = DisplayDevice::DISPLAY_PRIMARY;
- mVSyncEvent[0].header.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
- mVSyncEvent[0].vsync.count++;
- }
- } else {
- // Nobody is interested in vsync, so we just want to sleep.
- // h/w vsync should be disabled, so this will wait until we
- // get a new connection, or an existing connection becomes
- // interested in receiving vsync again.
- mCondition.wait(mLock);
- }
代码不长,逻辑也很简单,这里通过mCondition.waitRelative和mCondition.wait开始等待事件的发生了。
终于分析完这里,我们已经注册好了监听者,起好了线程,开始等待VSync的到来了。我们终于可以回到最最开始的问题里面,当VSync信号发出来时,系统到底是怎么处理的?
VSync信号的处理
我们在前面一章也提到了无论是软件方式还是硬件方式,SurfaceFlinger收到VSync信号后,处理函数都是onVSyncReceived函数:
- void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
- bool needsHwVsync = false;
- {
- if (type == 0 && mPrimaryHWVsyncEnabled) {
- needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
- }
- }
- if (needsHwVsync) {
- enableHardwareVsync();
- } else {
- disableHardwareVsync(false);
- }
- }
前文中我们也说过这段代码中最重要的一个函数调用—-addResyncSample,下面本文也将从这个函数开始继续VSync的研究。
真正开始处理VSync消息——addResyncSample
- bool DispSync::addResyncSample(nsecs_t timestamp) {
- size_t idx = (mFirstResyncSample + mNumResyncSamples) % MAX_RESYNC_SAMPLES;
- mResyncSamples[idx] = timestamp;
- if (mNumResyncSamples < MAX_RESYNC_SAMPLES) {
- mNumResyncSamples++;
- } else {
- mFirstResyncSample = (mFirstResyncSample + 1) % MAX_RESYNC_SAMPLES;
- }
- updateModelLocked();
- if (mNumResyncSamplesSincePresent++ > MAX_RESYNC_SAMPLES_WITHOUT_PRESENT) {
- resetErrorLocked();
- }
- if (runningWithoutSyncFramework) {
- // If we don’t have the sync framework we will never have
- // addPresentFence called. This means we have no way to know whether
- // or not we’re synchronized with the HW vsyncs, so we just request
- // that the HW vsync events be turned on whenever we need to generate
- // SW vsync events.
- return mThread->hasAnyEventListeners();
- }
- return mPeriod == 0 || mError > errorThreshold;
- }
粗略浏览下这个函数,发现前半部分其实在做一些简单的计数统计,重点实现显然是updateModelLocked函数:
- void DispSync::updateModelLocked() {
- if (mNumResyncSamples >= MIN_RESYNC_SAMPLES_FOR_UPDATE) {
- nsecs_t durationSum = 0;
- for (size_t i = 1; i < mNumResyncSamples; i++) {
- size_t idx = (mFirstResyncSample + i) % MAX_RESYNC_SAMPLES;
- size_t prev = (idx + MAX_RESYNC_SAMPLES – 1) % MAX_RESYNC_SAMPLES;
- durationSum += mResyncSamples[idx] – mResyncSamples[prev];
- }
- mPeriod = durationSum / (mNumResyncSamples – 1);
- double sampleAvgX = 0;
- double sampleAvgY = 0;
- double scale = 2.0 * M_PI / double(mPeriod);
- for (size_t i = 0; i < mNumResyncSamples; i++) {
- size_t idx = (mFirstResyncSample + i) % MAX_RESYNC_SAMPLES;
- nsecs_t sample = mResyncSamples[idx];
- double samplePhase = double(sample % mPeriod) * scale;
- sampleAvgX += cos(samplePhase);
- sampleAvgY += sin(samplePhase);
- }
- sampleAvgX /= double(mNumResyncSamples);
- sampleAvgY /= double(mNumResyncSamples);
- mPhase = nsecs_t(atan2(sampleAvgY, sampleAvgX) / scale);
- if (mPhase < 0) {
- mPhase += mPeriod;
- }
- if (traceDetailedInfo) {
- ATRACE_INT64(“DispSync:Period”, mPeriod);
- ATRACE_INT64(“DispSync:Phase”, mPhase);
- }
- mThread->updateModel(mPeriod, mPhase);
- }
- }
不得不说,前面大段的数学计算让人有些困惑,我们暂且跳过,先分析下主线流程,也就是mThread->updateModel(mPeriod, mPhase)这个调用:
DispSyncThread.updateModel的用途
- void updateModel(nsecs_t period, nsecs_t phase) {
- Mutex::Autolock lock(mMutex);
- mPeriod = period;
- mPhase = phase;
- mCond.signal();
- }
updateModel是DispSyncThread类的函数,这个函数本身代码很短,其实它的主要作用是mCond.signal发送一个信号给等待中的线程。那么究竟是谁在等待这个条件呢?
其实等待这个条件的正是DispSyncThread的循环函数:
- virtual bool threadLoop() {
- status_t err;
- nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
- nsecs_t nextEventTime = 0;
- while (true) {
- Vector<CallbackInvocation> callbackInvocations;
- nsecs_t targetTime = 0;
- { // Scope for lock
- Mutex::Autolock lock(mMutex);
- if (mStop) {
- return false;
- }
- if (mPeriod == 0) {
- err = mCond.wait(mMutex);
- if (err != NO_ERROR) {
- ALOGE(“error waiting for new events: %s (%d)”,
- strerror(-err), err);
- return false;
- }
- continue;
- }
- nextEventTime = computeNextEventTimeLocked(now);
- targetTime = nextEventTime;
- bool isWakeup = false;
- if (now < targetTime) {
- err = mCond.waitRelative(mMutex, targetTime – now);
- if (err == TIMED_OUT) {
- isWakeup = true;
- } else if (err != NO_ERROR) {
- ALOGE(“error waiting for next event: %s (%d)”,
- strerror(-err), err);
- return false;
- }
- }
- now = systemTime(SYSTEM_TIME_MONOTONIC);
- if (isWakeup) {
- mWakeupLatency = ((mWakeupLatency * 63) +
- (now – targetTime)) / 64;
- if (mWakeupLatency > 500000) {
- // Don’t correct by more than 500 us
- mWakeupLatency = 500000;
- }
- if (traceDetailedInfo) {
- ATRACE_INT64(“DispSync:WakeupLat”, now – nextEventTime);
- ATRACE_INT64(“DispSync:AvgWakeupLat”, mWakeupLatency);
- }
- }
- callbackInvocations = gatherCallbackInvocationsLocked(now);
- }
- if (callbackInvocations.size() > 0) {
- fireCallbackInvocations(callbackInvocations);
- }
- }
- return false;
- }
大量的时间相关的计算和状态的转变我们不再深入研究,我们来看下这个线程被通知唤醒之后做的两个主要的函数的处理,gatherCallbackInvocationsLocked和fireCallbackInvocations。
gatherCallbackInvocationsLocked的代码其实很简单:
- Vector<CallbackInvocation> gatherCallbackInvocationsLocked(nsecs_t now) {
- Vector<CallbackInvocation> callbackInvocations;
- nsecs_t ref = now – mPeriod;
- for (size_t i = 0; i < mEventListeners.size(); i++) {
- nsecs_t t = computeListenerNextEventTimeLocked(mEventListeners[i],
- ref);
- if (t < now) {
- CallbackInvocation ci;
- ci.mCallback = mEventListeners[i].mCallback;
- ci.mEventTime = t;
- callbackInvocations.push(ci);
- mEventListeners.editItemAt(i).mLastEventTime = t;
- }
- }
- return callbackInvocations;
- }
其实就是从mEventListeners取出之前注册的事件监听者,放入callbackInvocations中,等待后面的调用。至于监听者从何处而来?我们在前面已经给出了分析,在waitforevent时通过enableVSyncLocked注册的。
继续看下fireCallbackInvocations函数:
- void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
- for (size_t i = 0; i < callbacks.size(); i++) {
- callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
- }
- }`
我们目前只分析主线的走向,接下来调用了DispSyncSource的onDispSyncEvent在:
- virtual void onDispSyncEvent(nsecs_t when) {
- sp<VSyncSource::Callback> callback;
- {
- callback = mCallback;
- }
- if (callback != NULL) {
- callback->onVSyncEvent(when);
- }
- }
- 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();
- }
我们看到这里mCondition.broadcas发出了命令,那么EventThread中waitforEvent的等待就会被唤醒。而一旦唤醒,我们就回到了EventThread的loop中,我们来看下代码:
- bool EventThread::threadLoop() {
- DisplayEventReceiver::Event event;
- Vector< sp<EventThread::Connection> > signalConnections;
- signalConnections = waitForEvent(&event);
- // dispatch events to listeners…
- const size_t count = signalConnections.size();
- for (size_t i=0 ; i<count ; i++) {
- const sp<Connection>& conn(signalConnections[i]);
- // now see if we still need to report this event
- status_t err = conn->postEvent(event);
- 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.
- // FIXME: 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.
- ALOGW(“EventThread: dropping event (%08x) for connection %p”,
- event.header.type, conn.get());
- } 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.
- removeDisplayEventConnection(signalConnections[i]);
- }
- }
- return true;
- }
这里主要就是通过conn->postEvent来分发事件:
- status_t EventThread::Connection::postEvent(
- const DisplayEventReceiver::Event& event) {
- ssize_t size = DisplayEventReceiver::sendEvents(mChannel, &event, 1);
- return size < 0 ? status_t(size) : status_t(NO_ERROR);
- }
- ssize_t DisplayEventReceiver::sendEvents(const sp<BitTube>& dataChannel,
- Event const* events, size_t count)
- {
- return BitTube::sendObjects(dataChannel, events, count);
- }
其实看到这里的BitTube我们就明白了,在本文开始时候我们提到:
通过createEventConnection这样一个简单的方法,我们其实就注册了一个事件的监听者,得到了发送VSYNC事件通知的BitTube,然后监控这个BitTube中的套接字,并且指定了收到通知后的回调函数,MessageQueue::cb_eventReceiver。这样一旦VSync信号传来,函数cb_eventReceiver将被调用。
所以我们这里可以来看看MessageQueue::cb_eventReceiver函数了:
- int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {
- MessageQueue* queue = reinterpret_cast<MessageQueue *>(data);
- return queue->eventReceiver(fd, events);
- }
- int MessageQueue::eventReceiver(int fd, int events) {
- ssize_t n;
- DisplayEventReceiver::Event buffer[8];
- while ((n = DisplayEventReceiver::getEvents(mEventTube, buffer, 8)) > 0) {
- for (int i=0 ; i<n ; i++) {
- if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
- mHandler->dispatchInvalidate();
- break;
- }
- }
- }
- return 1;
- }
我们看到收到消息之后MessageQueue对消息进行了分发,我们目前走的是dispatchInvalidate。
- void MessageQueue::Handler::dispatchInvalidate() {
- if ((android_atomic_or(eventMaskInvalidate, &mEventMask) & eventMaskInvalidate) == 0) {
- mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE));
- }
- }
- void MessageQueue::Handler::handleMessage(const Message& message) {
- switch (message.what) {
- case INVALIDATE:
- android_atomic_and(~eventMaskInvalidate, &mEventMask);
- mQueue.mFlinger->onMessageReceived(message.what);
- break;
- case REFRESH:
- android_atomic_and(~eventMaskRefresh, &mEventMask);
- mQueue.mFlinger->onMessageReceived(message.what);
- break;
- case TRANSACTION:
- android_atomic_and(~eventMaskTransaction, &mEventMask);
- mQueue.mFlinger->onMessageReceived(message.what);
- break;
- }
- }
- void SurfaceFlinger::onMessageReceived(int32_t what) {
- ATRACE_CALL();
- switch (what) {
- case MessageQueue::TRANSACTION:
- handleMessageTransaction();
- break;
- case MessageQueue::INVALIDATE:
- handleMessageTransaction();
- handleMessageInvalidate();
- signalRefresh();
- break;
- case MessageQueue::REFRESH:
- handleMessageRefresh();
- break;
- }
- }
到了这里,就进入了SurfaceFlinger的处理流程,我们看到对于INVALIDATE的消息,实际上系统在处理过程中实际还是会发送一个Refresh消息。
这后面的处理过程,我会在博客后面的文章中详细讲解。
总结
我们用了两小节的篇幅来讲解了VSync在android系统中的作用,生成以及传递。详细说明了从VSync软硬件的生成,一直到事件如何做为一个INVALIDATE消息传递给了Surfaceflinger处理。我们再来回顾一下整体的流程图:
- 顶
- 5
- 踩
- 0

本章从Graphic的角度来分析Android系统中一个基础的view是如何被绘制出来的(只讨论硬件加速打开的场景):
下面我们以TextView这个类的onDraw函数为例看下,这个类是很多view的父类。
- protected void onDraw(Canvas canvas) {
- // Draw the background for this view
- super.onDraw(canvas);
- final int compoundPaddingLeft = getCompoundPaddingLeft();
- final int compoundPaddingTop = getCompoundPaddingTop();
- final int compoundPaddingRight = getCompoundPaddingRight();
- final int compoundPaddingBottom = getCompoundPaddingBottom();
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- final int right = mRight;
- final int left = mLeft;
- final int bottom = mBottom;
- final int top = mTop;
- final boolean isLayoutRtl = isLayoutRtl();
- final int offset = getHorizontalOffsetForDrawables();
- final int leftOffset = isLayoutRtl ? 0 : offset;
- final int rightOffset = isLayoutRtl ? offset : 0 ;
- final Drawables dr = mDrawables;
- if (dr != null) {
- int vspace = bottom – top – compoundPaddingBottom – compoundPaddingTop;
- int hspace = right – left – compoundPaddingRight – compoundPaddingLeft;
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableLeft != null) {
- canvas.save();
- canvas.translate(scrollX + mPaddingLeft + leftOffset,
- scrollY + compoundPaddingTop +
- (vspace – dr.mDrawableHeightLeft) / 2);
- dr.mDrawableLeft.draw(canvas);
- canvas.restore();
- }
- mTextPaint.setColor(color);
- mTextPaint.drawableState = getDrawableState();
- canvas.save();
- canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
- canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
- if (mEditor != null) {
- mEditor.onDraw(canvas, layout, highlight, highlightPaint, cursorOffsetVertical);
- } else {
- layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
- }
- if (mMarquee != null && mMarquee.shouldDrawGhost()) {
- final int dx = (int) mMarquee.getGhostOffset();
- mBidiFormat = BidiFormatter.getInstance();
- if (!mBidiFormat.isRtl(mText.toString())) {
- canvas.translate(+dx, 0.0f);
- } else {
- canvas.translate(-dx, 0.0f);
- }
- layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
- }
- //NAGSM_ANDROID_HQ_FRWK Gate +
- if(GateConfig.isGateEnabled() && GateConfig.isGateLcdtextEnabled())
- Log.i(“GATE”, “<GATE-M>LCDSTR:” + mText + “/LCDSTR</GATE-M>”);
- //NAGSM_ANDROID_HQ_FRWK Gate –
- canvas.restore();
- if (mTextStrikeThroughEnabled) {
- drawTextStrikethrough(canvas);
- }
- }
这个函数很长,所以我们还是按照老办法,分段来分析这个函数。
1 位置的计算
- final int compoundPaddingLeft = getCompoundPaddingLeft();
- final int compoundPaddingTop = getCompoundPaddingTop();
- final int compoundPaddingRight = getCompoundPaddingRight();
- final int compoundPaddingBottom = getCompoundPaddingBottom();
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- final int right = mRight;
- final int left = mLeft;
- final int bottom = mBottom;
- final int top = mTop;
- final boolean isLayoutRtl = isLayoutRtl();
- final int offset = getHorizontalOffsetForDrawables();
- final int leftOffset = isLayoutRtl ? 0 : offset;
- final int rightOffset = isLayoutRtl ? offset : 0 ;
首先是一系列位置的计算,这些在app编码设计时都可以修改赋值,应该都是一些屏幕上绝对坐标,后面的绘制会使用到这些坐标。
2 Drawable的绘制
- int vspace = bottom – top – compoundPaddingBottom – compoundPaddingTop;
- int hspace = right – left – compoundPaddingRight – compoundPaddingLeft;
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableLeft != null) {
- canvas.save();
- canvas.translate(scrollX + mPaddingLeft + leftOffset,
- scrollY + compoundPaddingTop +
- (vspace – dr.mDrawableHeightLeft) / 2);
- dr.mDrawableLeft.draw(canvas);
- canvas.restore();
- }
首先是view上下左右四个方向上的drawable的绘制(代码只贴了左边的drawable的绘制),我们是可以再view上设置这么多drawable的,如下图:
下面我们一个个来看下调用的函数。
2.1 Canvas.save 状态的保存
看代码之前,我们来看下api中对这个函数的说明:
- /**
- * Based on saveFlags, can save the current matrix and clip onto a private
- * stack. Subsequent calls to translate,scale,rotate,skew,concat or
- * clipRect,clipPath will all operate as usual, but when the balancing
- * call to restore() is made, those calls will be forgotten, and the
- * settings that existed before the save() will be reinstated.
- *
- * @param saveFlags flag bits that specify which parts of the Canvas state
- * to save/restore
- * @return The value to pass to restoreToCount() to balance this save()
- */
从api里我们可以看到这个函数的作用,它是为了保存当前的一些状态,比如矩阵,坐标等等。后面函数可以随意根据自己的需要进行一些translate,scale,rotate,skew,concat之类的操作,然后在restore调用后,这些变化将恢复到save时的状态。类似与游戏存档读档的功能。
在硬件加速打开的场景下,这里实际上是调用了GLES20Canvas的save函数,
- public int save() {
- return nSave(mRenderer, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
- }
- static jint android_view_GLES20Canvas_save(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer,
- jint flags) {
- return renderer->save(flags);
- }
我们看到,最终实现还是通过renderer来实现,基本上Canvas的命令都是这么一个过程。
- int DisplayListRenderer::save(int flags) {
- addStateOp(new (alloc()) SaveOp(flags));
- return OpenGLRenderer::save(flags);
- }
来看下save函数的具体实现,上面这个函数分为两部分,首先是通过addStateOp将具体操作对应的Op保存在了一个容器里面;接着通过调用OpenGLRenderer的函数,使调用立刻生效。我们来分别看下。
2.1.1 addStateOp操作命令的保存
我们首先要来理解一些Op的概念。在displaylist生效的状态下,硬件加速不会针对每一条命令都发送给GPU执行,而是先录制在displaylist里面,在最后通过回放的方式一起执行(这个详细的过程我们会在HWUI一章中讲到)。
目前我们可以这样理解Op,就是HWUI里面为了所有类型的操作都实现了对应的Op类,存储时只要存储一个类的对象即可。所有的Op都是DisplayListOp的子类,又分为两大类,一类是状态类的StateOp,一类是绘制类的DrawOp。Op在被回放时,会调用它们的replay函数,一般来说,StateOp会继续调用applyState来实现真正的逻辑,DrawOp则是调用applyDraw。
我们来看下这次保存的这个SaveOp是执行时是做了什么操作。
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.save(mFlags);
- }
这里无非就是执行了OpenGLRenderer的save方法,我们可以这样认为,OpenGLRenderer就可以实现renderer的所有功能,一旦我们调用OpenGLRenderer类的某个函数,那就意味着这条命令会被立刻执行(而不是先保存起来)。
2.1.2 save命令的执行
我们来继续看下save命令在OpenGLRenderer里面的执行(顺便提一句,从DisplayListRenderer::save代码我们看到,其实保存完Op后,save命令其实立刻被执行了。事实上,应该是所有的状态类Op在保存完之后都会立刻执行,而绘制类Op并不会执行,猜测原因应该是状态类Op其实是不需要GPU运算的,因此可以立刻执行):
- int OpenGLRenderer::save(int flags) {
- return saveSnapshot(flags);
- }
- int OpenGLRenderer::saveSnapshot(int flags) {
- mSnapshot = new Snapshot(mSnapshot, flags);
- return mSaveCount++;
- }
我们发现,这里其实就是将当前的状态保存在了一个快照Snapshot里面。所以我们下面来看下快照的概念。
2.1.3 快照Snapshot
本小节参考(http://blog.csdn.net/wind_hzx/article/details/20315471)
Snapshot可以称为快照,hwui就是通过Snapshot的方式来组织整个渲染状态的,Snapshot保存了当前渲染所需的视口,变换矩阵,裁剪区域,fbo信息等。同时还保存了当前Snapshot的上一个Snapshot,也就是通过栈的方式来组织Snapshot之间的关系的。
如图所示,在OpenGLRenderer类创建的时候,将会创建一个空的Snapshot,也就是FirstSnapshot,也可以称之为根节点,接着每次创建新的节点,都将会保存上一次的节点指针,新节点都用指针mSnapshot,来指向当年的节点,其实这就是用栈的形式来存储hwui的绘制状态,通过设置好Snapshot后,后续的绘图操作都会在mSnapshot(当前快照)上进行操作,当当前的绘制操作完成后,可以返回到上一次的Snapshot中,下一次的绘制操作不会影响上一次的绘制操作,但是上一次的绘制操作,可以通过设置来确定是否可以影响下一次Snapshot的渲染状态。
其实Snapshot很简单,本质上就是一个保存状态的快照,类似于网页快照的概念。唯一一点稍微复杂的逻辑,就是会根据flag的不同,对之前状态进行一些变换,进而生成当前的状态。
2.2 translate坐标变换
保存完状态后,函数调用translate开始进行绘制前的坐标变换。后面这种调用我们不再贴中途调用的流程,因为流程基本都是GLES20Canvas→JNI→DisplayListRenderer的顺序,我们直接看DisplayListRenderer的函数:
- void DisplayListRenderer::translate(float dx, float dy) {
- mHasTranslate = true;
- mTranslateX += dx;
- mTranslateY += dy;
- insertRestoreToCount();
- OpenGLRenderer::translate(dx, dy);
- }
- void DisplayListRenderer::insertRestoreToCount() {
- if (mRestoreSaveCount >= 0) {
- DisplayListOp* op = new (alloc()) RestoreToCountOp(mRestoreSaveCount);
- mDisplayListData->displayListOps.add(op);
- mRestoreSaveCount = -1;
- }
- }
和前面save函数一样,这里同样是保存了一个Op,然后立刻进行了坐标的变换。但是有趣的是,这里保存的一个Op的类型是一个RestoreToCountOp类型,看起来在执行时也压根没有做坐标变换(这个还可以理解,毕竟在录制过程中已经先做好了坐标变化,那么录制到的绘制命令坐标已经是更改过的,因此,实际上直接拿来执行即可),而且做了一些奇怪的操作(这部分代码相当奇怪,需要详细的展开研究)。
我们暂且跳过。
2.3 Drawable的绘制
接下来就是将当前的Drawable进行绘制,这里根据Drawable具体类型的不同,函数也不同,我们这里分析一个最常见的BitmapDrawable的draw过程:
- public void draw(Canvas canvas) {
- final BitmapState state = mBitmapState;
- if (state.mRebuildShader) {
- Shader.TileMode tmx = state.mTileModeX;
- Shader.TileMode tmy = state.mTileModeY;
- if (tmx == null && tmy == null) {
- state.mPaint.setShader(null);
- } else {
- state.mPaint.setShader(new BitmapShader(bitmap,
- tmx == null ? Shader.TileMode.CLAMP : tmx,
- tmy == null ? Shader.TileMode.CLAMP : tmy));
- }
- state.mRebuildShader = false;
- copyBounds(mDstRect);
- }
- Shader shader = state.mPaint.getShader();
- if (shader == null) {
- canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint);
- } else {
- canvas.drawRect(mDstRect, state.mPaint);
- }
- }
由于我们重点分析Graphic层面的逻辑,因此这里和上层有关的一些概念我们不再讲解,Shader有关的知识可以参考这篇博客:http://blog.csdn.net/ldj299/article/details/6166071,BitmapShader可以参考这篇:http://blog.csdn.net/sjf0115/article/details/7267532。
我们这里重点分析下canvas的两个draw函数:drawBitmap和drawRect。依然是直接看DisplayListRenderer的对应函数:
- status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
- bitmap = refBitmap(bitmap);
- paint = refPaint(paint);
- addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint));
- return DrawGlInfo::kStatusDone;
- }
这里添加了一个新的DrawBitmapOp,它在reply时会调用OpenGlRenderer的drawBitmap函数。绘制的执行过程,我们会在DisplayList的回放过程中一起分析。
3 layout.draw的绘制
onDraw函数的中段很长一部分是一些参数的设置,我们这里不详细研究上层的逻辑,我们接下来重点看下一个绘制函数,layout.draw。
- public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
- int cursorOffsetVertical) {
- final long lineRange = getLineRangeForDraw(canvas);
- int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
- int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
- if (lastLine < 0) return;
- drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
- firstLine, lastLine);
- drawText(canvas, firstLine, lastLine);
- }
我们直接看drawText,这个函数同样很长,但是大多数是view层面的一些参数设置,我们依然只关心Graphic层面的绘制:
其实重点只是这一点:
- {
- …
- if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
- canvas.drawText(buf, start, end, x, lbaseline, paint);
- } else {
- tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
- tl.draw(canvas, x, ltop, lbaseline, lbottom);
- }
- …
- }
看到了canvas的drawText,在打开硬件加速的情况下,我们可以按照老规矩直接看DisplayListRenderer的对应函数:
- status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int count,
- float x, float y, const float* positions, SkPaint* paint,
- float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode) {
- text = refText(text, bytesCount);
- positions = refBuffer<float>(positions, count * 2);
- paint = refPaint(paint);
- DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
- x, y, positions, paint, totalAdvance, bounds);
- addDrawOp(op);
- return DrawGlInfo::kStatusDone;
- }
这里依然是op的创建,然后添加,目前并不会真正执行,我们会在DisplayList的回放过程中一起分析。
小节
上面我们分析了从view怎么调用到Graphic层面,来将view分解到Graphic的Op层面。不管是什么view,整个流程都是一致的,可能只在最后调用时是创建了bitmap的op还是text的op等等的区别。因为本文主要从Graphic的层面分析绘制问题,因为我们这里没有详细展开view层面全部完整的设置和调用过程。但是通过上面的方法,我想我们可以分析任何一个View的创建Op的过程。
其实我们发现view层面生成Op的过程还是很简单的,无非只是根据参数的不同进行一些设置,然后根据要绘制内容的不同调用canvas不同的方法,本质上,Framework层面的调用和我们在app中使用canvas也并无明显不同。
Canvas调用成功后,通过GLES20Canvas→JNI→DisplayListRenderer的顺序,调用了DisplayListRenderer的方法,DisplayListRenderer一般只是直接生成对应Op并保存,等待后面一起调用OpenGLRenderer的方法真正执行。需要指出的是,对应state类的Op,并不会等待,而是在DisplayListRenderer中被调用时就会直接执行。
对于绘制类的Op,在保存后,会等待执行,至于执行的时机,以及具体如何执行,我们将在后文HWUI讲解完成之后再次进行分析Op命令的真正执行过程
- 顶
- 2
- 踩
- 0

1 概述
Android从3.0(API Level 11)开始,在绘制View的时候支持硬件加速,充分利用GPU的特性,使得绘制更加平滑。
实质上就是Android3.0以前,几乎所有的图形绘制都是由Skia完成,Skia是一个向量绘图库,使用CPU来进行运算;所以从Android3.0 开始,Google用hwui取代了Skia,准确的说,是推荐取代,因为Opengl的支持不完全,有少量图形api仍由Skia完成,多数view的绘制通过HWUI模块使用openGL的函数来实现。
由于硬件加速自身并非完美无缺,所以Android提供选项来打开或者关闭硬件加速,默认是关闭。一般我们会在两个级别上打开或者关闭硬件加速:
Application级别:<applicationandroid:hardwareAccelerated="true" ...>
Activity级别:<activity android:hardwareAccelerated="false" ...>
下面我们从代码级别来理解系统是如何实现硬件加速的。
2 开启硬件加速
在开始分析之前,我们先来看下ViewRootImpl中的一个基本流程图:
如上图所示,其实过程2就是打开硬件加速的过程,在ViewRootImpl的SetView函数中,通过调用enableHardwareAcceleration函数来开启硬件加速,我们来看下enableHardwareAcceleration的代码:
- private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
- mAttachInfo.mHardwareAccelerated = false;
- mAttachInfo.mHardwareAccelerationRequested = false;
- final boolean hardwareAccelerated =
- (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
- if (hardwareAccelerated) {
- if (!HardwareRenderer.isAvailable()) {
- return;
- }
- // Persistent processes (including the system) should not do
- // accelerated rendering on low-end devices. In that case,
- // sRendererDisabled will be set. In addition, the system process
- // itself should never do accelerated rendering. In that case, both
- // sRendererDisabled and sSystemRendererDisabled are set. When
- // sSystemRendererDisabled is set, PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED
- // can be used by code on the system process to escape that and enable
- // HW accelerated drawing. (This is basically for the lock screen.)
- final boolean fakeHwAccelerated = (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
- final boolean forceHwAccelerated = (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
- if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
- && forceHwAccelerated)) {
- // Don’t enable hardware acceleration when we’re not on the main thread
- if (!HardwareRenderer.sSystemRendererDisabled &&
- Looper.getMainLooper() != Looper.myLooper()) {
- Log.w(HardwareRenderer.LOG_TAG, “Attempting to initialize hardware “
- + “acceleration outside of the main thread, aborting”);
- return;
- }
- if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.destroy(true);
- }
- final boolean translucent = attrs.format != PixelFormat.OPAQUE;
- mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
- if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
- mAttachInfo.mHardwareAccelerated =
- mAttachInfo.mHardwareAccelerationRequested = true;
- }
- } else if (fakeHwAccelerated) {
- // The window had wanted to use hardware acceleration, but this
- // is not allowed in its process. By setting this flag, it can
- // still render as if it was accelerated. This is basically for
- // the preview windows the window manager shows for launching
- // applications, so they will look more like the app being launched.
- mAttachInfo.mHardwareAccelerationRequested = true;
- }
- }
- }
上面代码不长,而且注释很完善,所以我们主要来看下函数的最主体部分即可。
- static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
- switch (glVersion) {
- case 2:
- return Gl20Renderer.create(translucent);
- }
- throw new IllegalArgumentException(“Unknown GL version: “ + glVersion);
- }
- static HardwareRenderer create(boolean translucent) {
- if (GLES20Canvas.isAvailable()) {
- return new Gl20Renderer(translucent);
- }
- return null;
- }
- GlRenderer(int glVersion, boolean translucent) {
- mGlVersion = glVersion;
- mTranslucent = translucent;
- loadSystemProperties(null);
- }
我们发现这段代码最主要的作用就是创建了一个Gl20Renderer(继承自GlRenderer和HardwareRenderer)。创建时调用了通过loadSystemProperties函数加载了一些属性和调试选项,这里不展开,有兴趣可以详细研究一下。
创建好的Gl20Renderer保存在了mAttachInfo.mHardwareRenderer中,后面我们在使用硬件加速时就会用到。
3 硬件绘制
从前面图中我们可以知道,在ViewRootImpl绘制时,调用了draw函数。有两种选择,一种是软件绘制,一种是硬件绘制。
- if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
- // Draw with hardware renderer.
- mIsAnimating = false;
- mHardwareYOffset = yoff;
- mResizeAlpha = resizeAlpha;
- mCurrentDirty.set(dirty);
- dirty.setEmpty();
- attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
- animating ? null : mCurrentDirty);
- } else {
- // If we get here with a disabled & requested hardware renderer, something went
- // wrong (an invalidate posted right before we destroyed the hardware surface
- // for instance) so we should just bail out. Locking the surface with software
- // rendering at this point would lock it forever and prevent hardware renderer
- // from doing its job when it comes back.
- // Before we request a new frame we must however attempt to reinitiliaze the
- // hardware renderer if it’s in requested state. This would happen after an
- // eglTerminate() for instance.
- if (attachInfo.mHardwareRenderer != null &&
- !attachInfo.mHardwareRenderer.isEnabled() &&
- attachInfo.mHardwareRenderer.isRequested()) {
- try {
- attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder.getSurface());
- } catch (OutOfResourcesException e) {
- handleOutOfResourcesException(e);
- return;
- }
- mFullRedrawNeeded = true;
- scheduleTraversals();
- return;
- }
- if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
- return;
- }
- }
- }
软件绘制不在我们分析范围内,这里我们将在下一小节中来研究一下attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,animating ? null : mCurrentDirty)这个调用。
我们前面已经知道mHardwareRenderer其实是一个Gl20Renderer,那么我们来看下它的draw函数。
- void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
- Rect dirty) {
- if (canDraw()) {
- if (surfaceState != SURFACE_STATE_ERROR) {
- HardwareCanvas canvas = mCanvas;
- attachInfo.mHardwareCanvas = canvas;
- dirty = beginFrame(canvas, dirty, surfaceState);
- DisplayList displayList = buildDisplayList(view, canvas);
- int saveCount = 0;
- int status = DisplayList.STATUS_DONE;
- try {
- status = prepareFrame(dirty);
- if(mSurface.isValid() && dirty != null) {
- mSurface.setDirtyRect(dirty);
- }
- saveCount = canvas.save();
- callbacks.onHardwarePreDraw(canvas);
- if (displayList != null) {
- status |= drawDisplayList(attachInfo, canvas, displayList, status);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, “An error has occurred while drawing:”, e);
- } finally {
- callbacks.onHardwarePostDraw(canvas);
- canvas.restoreToCount(saveCount);
- view.mRecreateDisplayList = false;
- if (mDrawDelta > 0) {
- mFrameCount++;
- debugOverdraw(attachInfo, dirty, canvas, displayList);
- debugDirtyRegions(dirty, canvas);
- drawProfileData(attachInfo);
- }
- }
- onPostDraw();
- swapBuffers(status);
- }
- }
- }
上面这几十行代码,就是硬件绘制的最重要的几个流程调用,我们看到draw函数主要分为如下几步,beginFrame,buildDisplayList,prepareFrame,drawDisplayList,onHardwarePostDraw,onPostDraw,swapBuffers。整个详细的调用过程如下图:
从上图中我们可以看出,整个内部详细的调用过程还是比较复杂的,我们这里仔细的一一讲解一下。
1 beginFrame
这个函数的作用很简单:Notifies EGL that the frame is about to be rendered。
功能主要通过android_view_HardwareRenderer_beginFrame这个JNI函数实现:
- static void android_view_HardwareRenderer_beginFrame(JNIEnv* env, jobject clazz,
- jintArray size) {
- EGLDisplay display = eglGetCurrentDisplay();
- EGLSurface surface = eglGetCurrentSurface(EGL_DRAW);
- if (size) {
- EGLint value;
- jint* storage = env->GetIntArrayElements(size, NULL);
- eglQuerySurface(display, surface, EGL_WIDTH, &value);
- storage[0] = value;
- eglQuerySurface(display, surface, EGL_HEIGHT, &value);
- storage[1] = value;
- env->ReleaseIntArrayElements(size, storage, 0);
- }
- eglBeginFrame(display, surface);
在BeginFrame() 里获取EGLDisplay(用于显示) 和一个EGLSurface,OpenGL将在这个Surface上进行绘图。
eglBeginFrame主要是校验参数的合法性。
2 buildDisplayList
buildDisplayList是一个很重要也比较复杂的函数,通过这个函数,我们真正完成了视图绘制的录制工作,并将操作指令保存到了native层的DisplayList中,我们这里重点讲一下。
- private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
- if (mDrawDelta <= 0) {//如果根本没有新的绘制发生,使用之前的displaylist即可
- return view.mDisplayList;
- }
- view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
- == View.PFLAG_INVALIDATED;
- view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
- long buildDisplayListStartTime = startBuildDisplayListProfiling();
- canvas.clearLayerUpdates();
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, “getDisplayList”);
- DisplayList displayList = view.getDisplayList();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- endBuildDisplayListProfiling(buildDisplayListStartTime);
- return displayList;
- }
其实主要逻辑是通过view类的getDisplayList()方法实现的,这个方法通过递归的方式获得了整个View树的DisplayList:
- private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) {
- if (((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 ||
- displayList == null || !displayList.isValid() ||
- (!isLayer && mRecreateDisplayList))) {
- // Don’t need to recreate the display list, just need to tell our
- // children to restore/recreate theirs
- if (displayList != null && displayList.isValid() &&
- !isLayer && !mRecreateDisplayList) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- dispatchGetDisplayList();
- return displayList;
- }
- if (!isLayer) {
- // If we got here, we’re recreating it. Mark it as such to ensure that
- // we copy in child display lists into ours in drawChild()
- mRecreateDisplayList = true;
- }
- if (displayList == null) {
- displayList = mAttachInfo.mHardwareRenderer.createDisplayList(getClass().getName());
- // If we’re creating a new display list, make sure our parent gets invalidated
- // since they will need to recreate their display list to account for this
- // new child display list.
- invalidateParentCaches();
- }
- boolean caching = false;
- int width = mRight – mLeft;
- int height = mBottom – mTop;
- int layerType = getLayerType();
- final HardwareCanvas canvas = displayList.start(width, height);
- …
上面函数相当长,我们首先分析前半部分。暂时只看displaylist新创建时会走的逻辑:函数前半部分的流程基本是createDisplayList,invalidateParentCaches,displayList.start,我们来一点点分析。
2.1 createDisplayList
首先我们要创建一个displaylist,这里实际上只是创建了一个GLES20DisplayList类。
- public DisplayList createDisplayList(String name) {
- return new GLES20DisplayList(name);
- }
GLES20DisplayList是DisplayList的子类,这是一个影子类,没有实际用途,主要用来管理Native层的DisplayList的销毁。Native层的DisplayList在后面的start函数中才被真正创建。
2.2 invalidateParentCaches
这个函数的作用主要是,一旦我们重建了DisplayList,那么这个view必须清空之前的缓存,我们通过这个函数调用来清空缓存。
- protected void invalidateParentCaches() {
- if (mParent instanceof View) {
- ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
- }
- }
2.3 displayList.start
下面看下displayList.start函数:
- public HardwareCanvas start(int width, int height) {
- mValid = false;
- mCanvas = GLES20RecordingCanvas.obtain(this);
- mCanvas.start();
- mCanvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- mCanvas.onPreDraw(null);
- return mCanvas;
- }
2.3.1 GLES20RecordingCanvas.obtain
start函数真正开始构建DisplayList的上下文,先来看下obtain函数:
- static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) {
- GLES20RecordingCanvas canvas = sPool.acquire();
- if (canvas == null) {
- canvas = new GLES20RecordingCanvas();
- }
- canvas.mDisplayList = displayList;
- return canvas;
- }
obtain函数的作用从名字中就可以看出来,主要作用是获取到一个GLES20RecordingCanvas,这个类是 HardwareCanvas和GLES20Canvas的子类。
HardwareCanvas是一个抽象类,如果系统属性和应用程序指定使用硬件加速(现已成为默认),它将会被View(通过AttachInfo,如 下图所示) 引用来完成所有的图形绘制工作。GLES20Canvas 则是hardwareCanvas的实现,但它也只是一层封装而已,真正的实现在Native 层,通过jni (andriod_view_gles20Canvas.cpp)接口来访问底层的Renderer, 进而执行OpenGL的命令。 此外,GLES20Canvas还提供了一些静态接口,用于创建各类Renderer对象。
GLES20RecordingCanvas 继承GLES20Canvas, 通过它调用的OpenGL命令将会存储在DisplayList里面,而不会立即执行。
- protected GLES20Canvas(boolean record, boolean translucent) {
- mOpaque = !translucent;
- if (record) {
- mRenderer = nCreateDisplayListRenderer();
- } else {
- mRenderer = nCreateRenderer();
- }
- setupFinalizer();
- }
我们看到GLES20Canvas在创建时,由于我们现在需要录制命令,所以我们这里调用了nCreateDisplayListRenderer函数,创建的真正的用于渲染的本地对象DisplayListRenderer:
- static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env,
- jobject clazz) {
- return new DisplayListRenderer;
- }
DisplayListRenderer是OpenGLRenderer的子类,相比于OpenGLRenderer是直接绘制,DisplayListRenderer则是将绘制命令保存在它内部的mDisplayListData中,mDisplayListData的类型是DisplayListData:
- class DisplayListData : public LightRefBase<DisplayListData> {
- public:
- LinearAllocator allocator;
- Vector<DisplayListOp*> displayListOps;
- };
我们看到这里的重点必然是这个容器displayListOps,一定是它存储了绘制命令DisplayListOp,至于DisplayListOp的详细内容,我们后面再展开。
这样,通过nCreateDisplayListRenderer函数,我们创建了一个本地的DisplayListRenderer对象,他会将绘制命令保存在一个DisplayListData类的DisplayListOp容器里。
现在我们再次回到GLES20Canvas的创建过程中来,调用完nCreateDisplayListRenderer后,程序又继续调用了setupFinalizer,这个我们从名字就可以猜出来这是用于这个canvas回收的,这里我们就不再展开。
2.3.2 GLES20RecordingCanvas.start
回到displayList.start函数中来,创建好GLES20RecordingCanvas之后,继续调用了它的start函数。这个函数主要是清空自己和它的孩子的所有display的引用,这里不再展开。
2.3.3 GLES20RecordingCanvas.setViewport
继续调用setViewport来设置视口:
- public void setViewport(int width, int height) {
- mWidth = width;
- mHeight = height;
- nSetViewport(mRenderer, width, height);
- }
- static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject clazz,
- OpenGLRenderer* renderer, jint width, jint height) {
- renderer->setViewport(width, height);
- }
- void DisplayListRenderer::setViewport(int width, int height) {
- mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
- mWidth = width;
- mHeight = height;
- }
- void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
- loadIdentity();
- data[kScaleX] = 2.0f / (right – left);
- data[kScaleY] = 2.0f / (top – bottom);
- data[kScaleZ] = -2.0f / (far – near);
- data[kTranslateX] = -(right + left) / (right – left);
- data[kTranslateY] = -(top + bottom) / (top – bottom);
- data[kTranslateZ] = -(far + near) / (far – near);
- mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
- }
我们看到上层调用了视口,下层实际上还是通过DisplayListRenderer做了具体实现,主要是做了一个正交投影,具体计算我们这里暂时先不研究。
其实大多数opengl在Canvas层面上的调用都是这样,底层通过render实现了类似OpenGl的实现,后面类似函数我们将不再展开。
2.3.4 GLES20RecordingCanvas.onPreDraw
GLES20RecordingCanvas.onPreDraw函数其实也很简单,先来看代码:
- public int onPreDraw(Rect dirty) {
- if (dirty != null) {
- return nPrepareDirty(mRenderer, dirty.left, dirty.top, dirty.right, dirty.bottom,
- mOpaque);
- } else {
- return nPrepare(mRenderer, mOpaque);
- }
- }
- static int android_view_GLES20Canvas_prepare(JNIEnv* env, jobject clazz,
- OpenGLRenderer* renderer, jboolean opaque) {
- return renderer->prepare(opaque);
- }
- status_t OpenGLRenderer::prepare(bool opaque) {
- return prepareDirty(0.0f, 0.0f, mWidth, mHeight, opaque);
- }
- status_t OpenGLRenderer::prepareDirty(float left, float top,
- float right, float bottom, bool opaque) {
- setupFrameState(left, top, right, bottom, opaque);
- // Layer renderers will start the frame immediately
- // The framebuffer renderer will first defer the display list
- // for each layer and wait until the first drawing command
- // to start the frame
- if (mSnapshot->fbo == 0) {
- syncState();
- updateLayers();
- } else {
- return startFrame();
- }
- return DrawGlInfo::kStatusDone;
- }
本身这个onPreDraw其实是很简单的,无非是通过canvas来设置要显示的脏区域。最终逻辑依然是通过native层的OpenGLRenderer来实现,当然,这个实现最后变得有点复杂,因为这里又引入了一个新的概念,SnapShot:
- void OpenGLRenderer::setupFrameState(float left, float top,
- float right, float bottom, bool opaque) {
- mCaches.clearGarbage();
- mOpaque = opaque;
- mSnapshot = new Snapshot(mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- mSnapshot->fbo = getTargetFbo();
- mSaveCount = 1;
- mSnapshot->setClip(left, top, right, bottom);
- mTilingClip.set(left, top, right, bottom);
- }
我们这章引入的新概念实在过多,因此我们这里先跳过这个概念,可以参考这篇博客来先了解一下这个快照的概念,我们后面将单独开章节讲这个问题。
2.4 getDisplayList中段小结
由于getDisplayList实在是逻辑比较长,涉及的新概念比较多,我们这里暂时先暂停一下,回顾下前半部分的逻辑::函数前半部分的流程基本是createDisplayList,invalidateParentCaches,displayList.start。createDisplayList的作用是创建了一个java层的GLES20DisplayList,而displayList.start重点则是创建了一个native层GLES20RecordingCanvas,在创建Canvas的同时创建了DisplayListRenderer,DisplayListRenderer将需要执行的命令保存在内部的displaylist列表中,然后设置了视口,设置了脏区域,这里我们跳过了Snapshot这个新概念。
下面我们继续来看下getDisplayList后半段的代码。
- {
- …
- try {
- if (!isLayer && layerType != LAYER_TYPE_NONE) {
- if (layerType == LAYER_TYPE_HARDWARE) {
- final HardwareLayer layer = getHardwareLayer();
- if (layer != null && layer.isValid()) {
- canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
- } else {
- if (layer != null && !layer.isValid()) Log.e(“ViewSystem”, “View #2 getHardwareLayer() is not valid.”);
- canvas.saveLayer(0, 0, mRight – mLeft, mBottom – mTop, mLayerPaint,
- Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
- Canvas.CLIP_TO_LAYER_SAVE_FLAG);
- }
- caching = true;
- } else {
- buildDrawingCache(true);
- Bitmap cache = getDrawingCache(true);
- if (cache != null) {
- canvas.drawBitmap(cache, 0, 0, mLayerPaint);
- caching = true;
- }
- }
- } else {
- computeScroll();
- canvas.translate(-mScrollX, -mScrollY);
- if (!isLayer) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- }
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
- }
- …
2.5 开始生成绘制命令
上面这段代码涉及到两条分支,第一条分支又涉及到了新概念,layerType和HardwareLayer,这条分支比较复杂,我们单独放在下一章中单独讲解,我们现在重点看下第二条分支的代码:
- {
- …
- else {
- computeScroll();
- canvas.translate(-mScrollX, -mScrollY);
- if (!isLayer) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- }
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
- }
computeScroll计算滚动位置,和Graphic关系不大,不展开。
translate函数我们在上一节 Android 4.4 Graphic系统详解(4) 一个view的绘制之旅 中已经讲过了,这里不再展开。
其实就是创建了一个Op类的对象,然后将它保存在了DisplayListData的displayListOps容器里面,便于后面的执行。
回到view类getdisplaylist函数中来,说完了translate,下面就是重要的绘制生成流程了。
- {
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
其中dispatchDraw是通知view的所有孩子进行绘制(?),其实流程应该都是类似的,因此我们直接看当前view的draw流程,此函数将把View自身及其所有的子子孙孙绘制到给定的画布上。其画图过程主要分为以下六步:
1) 画背景
2) 如果需要,保存画布的层为未来的淡入淡出做好准备
3) 画View本身的内容
4) 画它的孩子
5) 如果需要,画淡入淡出的边缘并恢复层
6) 画装饰部分(如:滚动条)
上述六步中,2,5步常规情况下不需要,因此我们暂时跳过,只重点分析1,3,6步。
- public void draw(Canvas canvas) {
- // Step 1, draw the background, if needed
- int saveCount;
- if (!dirtyOpaque) {
- final Drawable background = mBackground;
- if (background != null) {
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- if (mBackgroundSizeChanged) {
- background.setBounds(0, 0, mRight – mLeft, mBottom – mTop);
- mBackgroundSizeChanged = false;
- }
- if ((scrollX | scrollY) == 0) {
- background.draw(canvas);
- } else {
- canvas.translate(scrollX, scrollY);
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
- }
- // skip step 2 & 5 if possible (common case)
- final int viewFlags = mViewFlags;
- boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
- boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
- if (!verticalEdges && !horizontalEdges) {
- // Step 3, draw the content
- if (!dirtyOpaque) onDraw(canvas);
- // Step 4, draw the children
- dispatchDraw(canvas);
- // Step 6, draw decorations (scrollbars)
- onDrawScrollBars(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().dispatchDraw(canvas);
- }
- // we’re done…
- return;
- }
- }
2.5.1 绘制背景
绘制背景很简单,只是调用了 background.draw(canvas)函数,其中background是一个Drawable类型,关于Drawable的绘制,依然可以参考“一个view的绘制之旅”一章中的Drawable的绘制小节讲解,这里不再展开。
2.5.2 onDraw 绘制内容
这一过程比较复杂,其实也就是我们在上一章“一个view的绘制之旅”中讲到的全部内容,具体内容可参考上一章。
2.5.3 绘制滚动条
绘制滚动条的代码比较长,但是按照我们一贯的习惯,我们只关注和Graphic相关的部分,在onDrawScrollBars函数中有横向和纵向ScrollBar的区别,但是对于下层的Graphic并无这种区别,都是调用ScrollBarDrawable的draw函数:
- public void draw(Canvas canvas) {
- boolean drawTrack = true;
- Rect r = getBounds();
- if (drawTrack) {
- drawTrack(canvas, r, vertical);
- }
- if (drawThumb) {
- int size = vertical ? r.height() : r.width();
- int thickness = vertical ? r.width() : r.height();
- int length = Math.round((float) size * extent / range);
- int offset = Math.round((float) (size – length) * mOffset / (range – extent));
- drawThumb(canvas, r, offset, length, vertical);
- }
- }
drawThumb中逻辑转来转去,最终其实还是在drawThumb中再次调用了Drawable的draw函数,这里不再详述。
最终,通过漫长的过程,我们终于完成了displaylist的构建,在完成了buildDisplayList的调用之后,我们回到Gl20Renderer类,继续来看下的draw函数,接下来就该Displaylist的真正执行了。
- 顶
- 2
- 踩
- 0