在硬件加速渲染环境中,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主要执行三个操作:
- 调用成员函数updateRootDisplayList构建或者更新应用程序窗口的Root Render Node的Display List。
- 调用成员函数registerAnimationRenderNode注册应用程序窗口动画相关的Render Node。
- 调用成员函数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中,它的执行逻辑如下所示:
- 调用成员函数syncFrameState将应用程序窗口的Display List、Render Property以及Display List引用的Bitmap等信息从Main Thread同步到Render Thread中。注意,在这个同步过程中,Main Thread是处于等待状态的。
- 如果成员函数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就通过这个条件变量来唤醒它。
- 调用成员变量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对象返回一些同步结果:
- 当这个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消息的时候,可以响应停止执行非异步动画的请求。
- 当这个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;