DisplayList 渲染过程分析

1,254 阅读7分钟

参考链接: blog.csdn.net/yangxu4536/…

在硬件加速渲染环境中,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进行的,如图所示:

img

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

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

img

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