Display List渲染过程分析

在硬件加速渲染环境中,Android应用程序窗口的UI渲染是分两步进行的。第一步是构建Display List,发生在应用程序进程的Main Thread中;第二步是渲染Display List,发生在应用程序进程的Render Thread中。Display List的渲染不是简单地执行绘制命令,而是包含了一系列优化操作,例如绘制命令的合并执行。

Android应用程序窗口的Root Render Node的Display List,包含了Android应用程序窗口所有的绘制命令,因此我们只要对Root Render Node的Display List进行渲染,就可以得到整个Android应用程序窗口的UI。

Android应用程序窗口的Display List的构建是通过Display List Renderer进行的,而渲染是通过Open GL Renderer进行的,如图所示:

在这里插入图片描述
Open GL Renderer只作用在Android应用程序窗口的Root Render Node的Display List上,这是因为Root Render Node的Display List包含了Android应用程序窗口所有的绘制命令。

Android应用程序窗口的Display List的渲染是由Render Thread执行的,不过是由Main Thread通知Render Thread执行的,如图所示:

在这里插入图片描述
Main Thread通过向Render Thread的TaskQueue添加一个drawFrame任务来通知Render Thread渲染Android应用程序窗口的UI。

Android应用程序窗口的Display List构建完成之后,Main Thread就马上向Render Thread发出渲染命令,如下所示:

public class ThreadedRenderer extends HardwareRenderer {
       
    ......    
    
    @Override    
    void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
       
        ......    
    
        updateRootDisplayList(view, callbacks);    
        ......    
    
        if (attachInfo.mPendingAnimatingRenderNodes != null) {
       
            final int count = attachInfo.mPendingAnimatingRenderNodes.size();    
            for (int i = 0; i < count; i++) {
       
                registerAnimatingRenderNode(    
                        attachInfo.mPendingAnimatingRenderNodes.get(i));    
            }    
            attachInfo.mPendingAnimatingRenderNodes.clear();    
            // We don't need this anymore as subsequent calls to    
            // ViewRootImpl#attachRenderNodeAnimator will go directly to us.    
            attachInfo.mPendingAnimatingRenderNodes = null;    
        }    
    
        int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,    
                recordDuration, view.getResources().getDisplayMetrics().density);    
        if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
     
            attachInfo.mViewRootImpl.invalidate();  
        }  
   
    }    
    
    ......    
}   

ThreadedRenderer类的成员函数draw主要执行三个操作:

  1. 调用成员函数updateRootDisplayList构建或者更新应用程序窗口的Root Render Node的Display List。
  2. 调用成员函数registerAnimationRenderNode注册应用程序窗口动画相关的Render Node。
  3. 调用成员函数nSyncAndDrawFrame渲染应用程序窗口的Root Render Node的Display List。

其中,第一个操作在前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文已经分析,第二个操作在接下来的一篇文章中分析,这篇文章主要关注第三个操作,即应用程序窗口的Root Render Node的Display List的渲染过程,即ThreadedRenderer类的成员函数nSyncAndDrawFrame的实现。

ThreadedRenderer类的成员函数nSyncAndDrawFrame是一个JNI函数,由Native层的函数android_view_ThreadedRenderer_syncAndDrawFrame实现,如下所示:

static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,  
        jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) {
     
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);  
    return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density);  
} 

参数proxyPtr描述的是一个RenderProxy对象,这里调用它的成员函数syncAndDrawFrame渲染应用程序窗口的Display List。

RenderProxy类的成员函数syncAndDrawFrame的实现如下所示:

int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,  
        float density) {
     
    mDrawFrameTask.setDensity(density);  
    return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos);  
}  

RenderProxy类的成员变量mDrawFrameTask指向的是一个DrawFrameTask对象。在前面Android应用程序UI硬件加速渲染环境初始化过程分析一文提到,这个DrawFrameTask对象描述的是一个用来执行渲染任务的Task,这里调用它的成员函数drawFrame渲染应用程序窗口的下一帧,也就是应用程序窗口的Display List。

DrawFrameTask的成员函数drawFrame的实现如下所示:

int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
     
    ......  
  
    mSyncResult = kSync_OK;  
    ......  
  
    postAndWait();  
  
    ......  
  
    return mSyncResult;  
}  

DrawFrameTask的成员函数drawFrame最主要的操作就是调用另外一个成员函数postAndWait往Render Thread的Task Queue抛一个消息,并且进入睡眠状态,等待Render Thread在合适的时候唤醒。

DrawFrameTask的成员函数postAndWait的实现如下所示:

void DrawFrameTask::postAndWait() {
     
    AutoMutex _lock(mLock);  
    mRenderThread->queue(this);  
    mSignal.wait(mLock);  
}  

由于DrawFrameTask类描述的就是一个可以添加到Render Thread的Task Queue的Task,因此DrawFrameTask的成员函数postAndWait就将当前正在处理的DrawFrameTask对象添加到由成员变量mRenderThread描述的Render Thread的Task Queue,并且在另外一个成员变量mSignal描述的一个条件变量上进行等待。

从前面Android应用程序UI硬件加速渲染环境初始化过程分析一文可以知道,添加到Render Thread的Task Queue的Task被处理时,它的成员函数run就会被调用,因此接下来DrawFrameTask类的成员函数run就会被调用,它的实现如下所示:

void DrawFrameTask::run() {
     
    ......  
  
    bool canUnblockUiThread;  
    bool canDrawThisFrame;  
    {
     
        TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());  
        canUnblockUiThread = syncFrameState(info);  
        canDrawThisFrame = info.out.canDrawThisFrame;  
    }  
  
    // Grab a copy of everything we need  
    CanvasContext* context = mContext;  
  
    // From this point on anything in "this" is *UNSAFE TO ACCESS*  
    if (canUnblockUiThread) {
     
        unblockUiThread();  
    }  
  
    if (CC_LIKELY(canDrawThisFrame)) {
     
        context->draw();  
    }  
  
    if (!canUnblockUiThread) {
     
        unblockUiThread();  
    }  
}  

要理解这个函数首先要理解应用程序进程的Main Thread和Render Thread是如何协作的。从前面的分析可以知道,Main Thread请求Render Thread执行Draw Frame Task的时候,不能马上返回,而是进入等待状态。等到Render Thread从Main Thread同步完绘制所需要的信息之后,Main Thread才会被唤醒。

那么,Render Thread要从Main Thread同步什么信息呢?原来,Main Thread和Render Thread都各自维护了一份应用程序窗口视图信息。各自维护了一份应用程序窗口视图信息的目的,就是为了可以互不干扰,进而实现最大程度的并行。其中,Render Thread维护的应用程序窗口视图信息是来自于Main Thread的。因此,当Main Thread维护的应用程序窗口信息发生了变化时,就需要同步到Render Thread去。

应用程序窗口的视图信息包含图1所示的各个Render Node的Display List、Property以及Display List引用的Bitmap。在RenderNode类中,有六个成员变量是与Display List和Property相关的,如下所示:

class RenderNode : public VirtualLightRefBase {
     
public:  
    ......  
  
    ANDROID_API void setStagingDisplayList(DisplayListData* newData);  
    ......  
  
    const RenderProperties& stagingProperties() {
     
        return mStagingProperties;  
    }  
    ......  
  
private:  
    ......  
  
    uint32_t mDirtyPropertyFields;  
    RenderProperties mProperties;  
    RenderProperties mStagingProperties;  
  
    bool mNeedsDisplayListDataSync;  
    // WARNING: Do not delete this directly, you must go through deleteDisplayListData()!  
    DisplayListData* mDisplayListData;  
    DisplayListData* mStagingDisplayListData;  
   
    ......  
};  

其中,成员变量mStagingProperties描述的Render Properties和成员变量mStagingDisplayListData描述的Display List Data由Main Thread维护,而成员变量mProperties描述的Render Properties和成员变量mDisplayListData描述的Display List Data由Render Thread维护。

这一点可以从前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文看出。当Main Thread构建完成应用程序窗口的Display List之后,就会调用RenderNode类的成员函数setStagingDisplayList将其设置到Root Render Node的成员变量mStagingDisplayListData中去。而当应用程序窗口某一个View的Property发生变化时,就会调用RenderNode类的成员函数mutateStagingProperties获得成员变量mStagingProperties描述的Render Properties,进而修改相应的Property。

当Main Thread维护的Render Properties发生变化时,成员变量mDirtyPropertyFields的值就不等于0,其中不等于0的位就表示是哪一个具体的Property发生了变化,而当Main Thread维护的Display List Data发生变化时,成员变量mNeedsDisplayListDataSync的值就等于true,表示要从Main Thread同步到Render Thread。

另外,在前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文分析将一个Bitmap绘制命令转化为一个DrawBitmapOp记录在Display List时,Bitmap会被增加一个引用,如下所示:

status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
     
    bitmap = refBitmap(bitmap);  
    paint = refPaint(paint);  
  
    addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint));  
    return DrawGlInfo::kStatusDone;  
}  

参数bitmap描述的SkBitmap通过调用DisplayListRenderer类的成员函数refBitmap进行使用,它的实现如下所示:

class ANDROID_API DisplayListRenderer: public StatefulBaseRenderer {
     
public:  
    ......  
  
    inline const SkBitmap* refBitmap(const SkBitmap* bitmap) {
     
        ......  
        mDisplayListData->bitmapResources.add(bitmap);  
        mCaches.resourceCache.incrementRefcount(bitmap);  
        return bitmap;  
    }  
  
    ......  
};  

DisplayListRenderer类的成员函数refBitmap在增加参数bitmap描述的一个SkBitmap的引用计数之前,会将它保存在成员变量mDisplayListData指向的一个DisplayListData对象的成员变量bitmapResources描述的一个Vector中。

上述情况是针对调用GLES20Canvas类的以下成员函数drawBitmap绘制一个Bitmap时发生的情况:

class GLES20Canvas extends HardwareCanvas {
     
    ......  
  
    @Override  
    public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
     
        throwIfCannotDraw(bitmap);  
        final long nativePaint = paint == null ? 0 : paint.mNativePaint;  
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);  
    }  
  
    ......  
}  

我们还可以调用GLES20Canvas类的另外一个重载版本的成员函数drawBitmap绘制一个Bitmap,如下所示:

class GLES20Canvas extends HardwareCanvas {
     
    ......  
  
    @Override  
    public void drawBitmap(int[] colors, int offset, int stride, float x, float y,  
            int width, int height, boolean hasAlpha, Paint paint) {
     
        ......  
  
        final long nativePaint = paint == null ? 0 : paint.mNativePaint;  
        nDrawBitmap(mRenderer, colors, offset, stride, x, y,  
                width, height, hasAlpha, nativePaint);  
    }  
  
    ......  
}  

GLES20Canvas类这个重载版本的成员函数drawBitmap通过一个int数组来指定要绘制的Bitmap。这个int数组是由应用程序自己管理的,并且会被封装成一个SkBitmap,最终由DisplayListRenderer类的成员函数drawBitmapData将该Bitmap绘制命令封装成一个DrawBitmapDataOp记录在Display List中,如下所示:

status_t DisplayListRenderer::drawBitmapData(const SkBitmap* bitmap, const SkPaint* paint) {
     
    bitmap = refBitmapData(bitmap);  
    paint = refPaint(paint);  
  
    addDrawOp(new (alloc()) DrawBitmapDataOp(bitmap, paint));  
    return DrawGlInfo::kStatusDone;  
}  

DisplayListRenderer类的成员函数drawBitmapData通过另外一个成员函数refBitmapData来增加参数bitmap描述的SkBitmap的引用,如下所示:

class ANDROID_API DisplayListRenderer: public StatefulBaseRenderer {
     
public:  
    ......  
  
    inline const SkBitmap* refBitmapData(const SkBitmap* bitmap) {
     
        mDisplayListData->ownedBitmapResources.add(bitmap);  
        mCaches.resourceCache.incrementRefcount(bitmap);  
        return bitmap;  
    }  
  
    ......  
};  

与前面分析的DisplayListRenderer类的成员函数refBitmap不同,DisplayListRenderer类的成员函数refBitmapData将参数bitmap描述的SkBitmap保存在成员变量mDisplayListData指向的一个DisplayListData对象的成员变量ownedBitmapResources描述的一个Vector中。这是由于前者引用的SkBitmap使用的底层存储是由应用程序提供和管理的,而后者引用的SkBitmap使用的底层存储是在SkBitmap内部创建和管理的。这个区别在接下来分析Bitmap的同步过程时会进一步得到体现。

Display List引用的Bitmap的同步方式与Display List和Render Property的同步方式有所不同。在同步Bitmap的时候,Bitmap将作为一个Open GL纹理上传到GPU去被Render Thread使用。

有了这些背景知识之后 ,再回到DrawFrameTask类的成员函数run中,它的执行逻辑如下所示:

  1. 调用成员函数syncFrameState将应用程序窗口的Display List、Render Property以及Display List引用的Bitmap等信息从Main Thread同步到Render Thread中。注意,在这个同步过程中,Main Thread是处于等待状态的。
  2. 如果成员函数syncFrameState能顺利地完成信息同步,那么它的返回值canUnblockUiThread就会等于true,表示在Render Thread渲染应用程序窗口的下一帧之前,就可以唤醒Main Thread了。否则的话,就要等到Render Thread渲染应用程序窗口的下一帧之后,才能唤醒Main Thread。唤醒Render Thread是通过调用成员函数unblockUiThread来完成的,如下所示:
void DrawFrameTask::unblockUiThread() {
     
    AutoMutex _lock(mLock);  
    mSignal.signal();  
}  

前面Main Thread就刚好是等待在DrawFrameTask类的成员变量mSignal描述的一个条件变量之上的,所以现在Render Thread就通过这个条件变量来唤醒它。

  1. 调用成员变量mContext描述的一个CanvasContext对象的成员函数draw渲染应用程序窗口的Display List,不过前提是当前帧能够进行绘制。什么时候当前帧不能够进行绘制呢?我们知道,应用程序进程绘制好一个窗口之后,得到的图形缓冲区要交给Surface Flinger进行合成,最后才能显示在屏幕上。Surface Flinger为每一个窗口都维护了一个图形缓冲区队列。当这个队列等待合成的图形缓冲区的个数大于等于2时,就表明Surface Flinger太忙了。因此这时候就最好不再向它提交图形缓冲区,这就意味着应用程序窗口的当前帧不能绘制了,也就是会被丢弃。

注意,Render Thread渲染应用程序窗口的Display List的时候,Main Thread有可能是处于等待状态,也有可能不是处于等待状态。这取决于前面的信息同步结果。信息同步结果是通过一个TreeInfo来描述的。当Main Thread不是处于等待状态时,它就可以马上处理其它事情了,例如构建应用程序窗口下一帧时使用的Display List。这样就可以做到Render Thread在绘制应用程序窗口的当前帧的同时,Main Thread可以并行地去构建应用程序窗口的下一帧的Display List。这一点也是Android 5.0引进Render Thread的好处所在。

接下来,我们就先分析应用程序窗口绘制信息的同步过程,即DrawFrameTask类的成员函数syncFrameState的实现,接着再分析应用程序窗口的Display List的渲染过程,即CanvasContext类的成员函数draw的实现。

DrawFrameTask类的成员函数syncFrameState的实现如下所示:

bool DrawFrameTask::syncFrameState(TreeInfo& info) {
     
    ......  
     
    Caches::getInstance().textureCache.resetMarkInUse();  
  
    for (size_t i = 0; i < mLayers.size(); i++) {
     
        mContext->processLayerUpdate(mLayers[i].get());  
    }  
    ......  
    mContext->prepareTree(info);  
  
    if (info.out.hasAnimations) {
     
        if (info.out.requiresUiRedraw) {
     
            mSyncResult |= kSync_UIRedrawRequired;  
        }  
    }  
    // If prepareTextures is false, we ran out of texture cache space  
    return info.prepareTextures;  
}  

应用程序进程有一个Caches单例。这个Caches单例有一个成员变量textureCache,它指向的是一个TextureCache对象。这个TextureCache对象用来缓存应用程序窗口在渲染过程中用过的Open GL纹理。在同步应用程序窗口绘制信息之前,DrawFrameTask类的成员函数syncFrameState首先调用这个TextureCache对象的成员函数resetMarkInUse将缓存的Open GL纹理标记为未使用状态。

在前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文提到,当TextureView有更新时,Native层会将一个与它关联的DeferredLayerUpdater对象保存在DrawFrameTask类的成员变量mLayers描述的一个vector中。也就是说,保存在这个vector中的DeferredLayerUpdater对象,都是需要进一步处理的。需要做的处理就是从与TextureView关联的SurfaceTexture中读出下一个可用的图形缓冲区,并且将该图形缓冲区封装成一个Open GL纹理。这是通过调用DrawFrameTask类的成员变量mContext指向的一个CanvasContext对象的成员函数processLayerUpdate来实现的。

CanvasContext类的成员函数processLayerUpdate的实现如下所示:

void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
     
    bool success = layerUpdater->apply();  
    ......  
} 

CanvasContext类的成员函数processLayerUpdate主要是调用参数layerUpdater描述的一个DeferredLayerUpdater对象的成员函数apply读出下一个可用的图形缓冲区,并且将该图形缓冲区封装成一个Open GL纹理,以便后面可以对它进行渲染。

DeferredLayerUpdater类的成员函数apply的实现如下所示:

bool DeferredLayerUpdater::apply() {
     
    bool success = true;  
    ......  
  
    if (mSurfaceTexture.get()) {
     
        ......  
        if (mUpdateTexImage) {
     
            mUpdateTexImage = false;  
            doUpdateTexImage();  
        }  
        ......  
    }  
    return success;  
}  

DeferredLayerUpdater类的成员变量mSurfaceTexture指向的一个是GLConsumer对象。这个GLConsumer对象用来描述与当前正在处理的DeferredLayerUpdater对象关联的TextureView对象所使用的一个SurfaceTexture对象的读端。也就是说,通过这个GLConsumer对象可以将关联的TextureView对象的下一个可用的图形缓冲区读取出来。

从前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文可以知道,当一个TextureView有可用的图形缓冲区时,与它关联的DeferredLayerUpdater对象的成员变量mUpdateTexImage值会被设置为true。这时候如果当前正在处理的DeferredLayerUpdater对象的成员变量mSurfaceTexture指向了一个GLConsumer对象,那么现在就是时候去读取可用的图形缓冲区了。这是通过调用DeferredLayerUpdater类的成员函数doUpdateTexImage来实现的。

DeferredLayerUpdater类的成员函数doUpdateTexImage的实现如下所示:

void DeferredLayerUpdater::doUpdateTexImage() {
     
    if (mSurfaceTexture->updateTexImage() == NO_ERROR) {
     
        ......  
  
        GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget();  
  
        LayerRenderer::updateTextureLayer(mLayer, mWidth, mHeight,  
                !mBlend, forceFilter, renderTarget, transform);  
    }  
}  

DeferredLayerUpdater类的成员函数doUpdateTexImage调用成员变量mSurfaceTexture指向的一个GLConsumer对象的成员函数updateTexImage读出可用的图形缓冲区,并且将该图形缓冲区封装成一个Open GL纹理。这个Open GL纹理可以通过调用上述的GLConsumer对象的成员函数getCurrentTextureTarget获得了。

接下来DeferredLayerUpdater类的成员函数doUpdateTexImage调用LayerRenderer类的静态成员函数updateTextureLayer将获得的Open GL纹理关联给成员变量mLayer描述的一个Layer对象。

LayerRenderer类的静态成员函数updateTextureLayer的实现如下所示:

void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,  
        bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform) {
     
    if (layer) {
     
        ......  
  
        if (renderTarget != layer->getRenderTarget()) {
     
            layer->setRenderTarget(renderTarget);  
            ......  
        }  
    }  
} 

LayerRenderer类的静态成员函数updateTextureLayer主要就是将参数renderTarget描述的Open GL纹理设置给参数layer描述的Layer对象。这是通过调用Layer类的成员函数setRenderTarget实现的。一个Layer对象关联了Open GL纹理之后,以后就可以进行渲染了。

这一步执行完成之后,如果应用程序窗口存在需要更新的TextureView,那么这些TextureView就更新完毕,也就是这些TextureView下一个可用的图形缓冲区已经被读出,并且封装成了Open GL纹理。回到前面分析的DrawFrameTask类的成员函数syncFrameState中,接下来要做的事情是将Main Thread维护的Display List等信息同步到Render Thread中。这是通过调用DrawFrameTask类的成员变量mContext指向的一个CanvasContext对象的成员函数prepareTree实现的。

CanvasContext对象的成员函数prepareTree执行完毕之后,会通过参数info描述的一个TreeInfo对象返回一些同步结果:

  1. 当这个TreeInfo对象的成员变量out指向的一个Out对象的成员变量hasAnimations等于true时,表示应用程序窗口存在未完成的动画。如果这些未完成的动画至少存在一个是非异步动画时,上述Out对象的成员变量requiresUiRedraw的值就会被设置为true。这时候DrawFrameTask类的成员变量mSyncResult的kSync_UIRedrawRequired位就会被设置为1。所谓非异步动画,就是那些在执行过程可以停止的动画。这个停止执行的逻辑是由Main Thread执行的,例如,Main Thread可以响应用户输入停止执行一个非异步动画。从前面分析可以知道,DrawFrameTask类的成员变量mSyncResult的值最后将会返回给Java层的ThreadedRenderer类的成员函数draw。ThreadedRenderer类的成员函数draw一旦发现该值的kSync_UIRedrawRequired位被设置为1,那么就会向Main Thread的消息队列发送一个INVALIDATE消息,以便在处理这个INVALIDATE消息的时候,可以响应停止执行非异步动画的请求。
  2. 当这个TreeInfo对象的成员变量prepareTextures的值等于true时,表示应用程序窗口的Display List引用到的Bitmap均已作为Open GL纹理上传到了GPU。这意味着应用程序窗口的Display List引用到的Bitmap已全部同步完成。在这种情况下,Render Thread在渲染下一帧之前,就可以唤醒Main Thread。另一方面,如果上述TreeInfo对象的成员变量prepareTextures的值等于false,就意味着应用程序窗口的Display List引用到的某些Bitmap不能成功地作为Open GL纹理上传到GPU,这时候Render Thread在渲染下一帧之后,才可以唤醒Main Thread,防止这些未能作为Open GL纹理上传到GPU的Bitmap一边被Render Thread渲染,一边又被Main Thread修改。那么什么时候应用程序窗口的Display List引用到的Bitmap会不能成功地作为Open GL纹理上传到GPU呢?一个应用程序进程可以创建的Open GL纹理是有大小限制的,如果超出这个限制,那么就会导至某些Bitmap不能作为Open GL纹理上传到GPU。

接下来,我们就继续分析CanvasContext类的成员函数prepareTree的实现,以便可以了解应用程序窗口的Display List等信息的同步过程,如下所示:

void CanvasContext::prepareTree(TreeInfo& info) {
     
    ......  
  
    info.renderer = mCanvas;  
    ......  
  
    mRootRenderNode->prepareTree(info);  
  
    ......  
  
    int runningBehind = 0;  
    // TODO: This query is moderately expensive, investigate adding some sort  
    // of fast-path based off when we last called eglSwapBuffers() as well as  
    // last vsync time. Or something.  
    mNativeWindow->query(mNativeWindow.get(),  
            NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);  
    info.out.canDrawThisFrame = !runningBehind;
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值