8.5.2 SF工作线程分析

SF中的工作线程就是用来做图像混合的,比起AudioFlinger来,它相当简单,下面是它的代码:


[—>SurfaceFlinger.cpp]

bool SurfaceFlinger:threadLoop()

{

waitForEvent();//①等待什么事件呢?

if(UNLIKELY(mConsoleSignals)){

handleConsoleEvents();

}

if(LIKELY(mTransactionCount==0)){

const uint32_t mask=eTransactionNeeded|eTraversalNeeded;

uint32_t transactionFlags=getTransactionFlags(mask);

if(LIKELY(transactionFlags)){

//Transaction(事务)处理,放到本节最后来讨论。

handleTransaction(transactionFlags);

}

}

//②处理PageFlipping工作。

handlePageFlip();

const DisplayHardware&hw(graphicPlane(0).displayHardware());

if(LIKELY(hw.canDraw()&&!isFrozen())){

//③处理重绘。

handleRepaint();

hw.compositionComplete();

//④投递BackBuffer。

unlockClients();

postFramebuffer();

}else{

unlockClients();

usleep(16667);

}

return true;

}


ThreadLoop一共有四个关键点(即①~④),这里分析除Transaction外的三个关键点。

1.waitForEvent

SF工作线程一上来就等待事件,那会是什么事件呢?来看代码:


[—>SurfaceFlinger.cpp]

void SurfaceFlinger:waitForEvent()

{

while(true){

nsecs_t timeout=-1;

const nsecs_t freezeDisplayTimeout=ms2ns(5000);

……

MessageList:value_type msg=mEventQueue.waitMessage(timeout);

……//还有一些和冻屏相关的内容。

if(msg!=0){

switch(msg->what){

//千辛万苦就等这一个重绘消息。

case MessageQueue:INVALIDATE:

return;

}

}

}

}


SF收到重绘消息后,将退出等待。那么,是谁发送的这个重绘消息呢?还记得在unlockCanvasAndPost函数中调用的signal吗?它在SF端的实现代码如下:


[—>SurfaceFlinger]

void SurfaceFlinger:signal()const{

const_cast<SurfaceFlinger*>(this)->signalEvent();

}

void SurfaceFlinger:signalEvent(){

mEventQueue.invalidate();//往消息队列中加入INVALIDATE消息。

}


2.handlePageFlip分析

SF工作线程从waitForEvent中返回后,下一步要做的就是处理事务和handlePageFlip了。先看handlePageFlip,从名字上可知,它和PageFlipping工作有关。

注意 事务处理将在8.5.3节中介绍。

代码如下所示:


[—>SurfaceFlinger.cpp]

void SurfaceFlinger:handlePageFlip()

{

bool visibleRegions=mVisibleRegionsDirty;

/*

还记得前面所说的mCurrentState吗?它保存了所有显示层的信息,而绘制的时候使用的mDrawingState则保存了当前需要显示的显示层信息。

*/

LayerVector&currentLayers=

const_cast<LayerVector&>(mDrawingState.layersSortedByZ);

//①调用lockPageFlip。

visibleRegions|=lockPageFlip(currentLayers);

const DisplayHardware&hw=graphicPlane(0).displayHardware();

//取得屏幕的区域。

const Region screenRegion(hw.bounds());

if(visibleRegions){

Region opaqueRegion;

computeVisibleRegions(currentLayers,mDirtyRegion,opaqueRegion);

mWormholeRegion=screenRegion.subtract(opaqueRegion);

mVisibleRegionsDirty=false;

}

//②调用unlockPageFlip。

unlockPageFlip(currentLayers);

mDirtyRegion.andSelf(screenRegion);

}


hanldePageFlip调用了两个看起来是一对的函数:lockPageFlip和unlockPageFlip。这两个函数会干些什么呢?

(1)lockPageFlip分析

先看lockPageFlip函数,代码如下所示:


[—>SurfaceFlinger.cpp]

bool SurfaceFlinger:lockPageFlip(const LayerVector&currentLayers)

{

bool recomputeVisibleRegions=false;

size_t count=currentLayers.size();

sp<LayerBase>const*layers=currentLayers.array();

for(size_t i=0;i<count;i++){

const sp<LayerBase>&layer=layers[i];

//调用每个显示层的lockPageFlip。

layer->lockPageFlip(recomputeVisibleRegions);

}

return recomputeVisibleRegions;

}


假设当前的显示层是Layer类型,那么得转到Layer类去看它的lockPageFlip函数了,代码如下所示:


[—>Layer.cpp]

void Layer:lockPageFlip(bool&recomputeVisibleRegions)

{

//lcblk是SharedBufferServer类型,调用retireAndLock函数将返回FrontBuffer的

//索引号。

ssize_t buf=lcblk->retireAndLock();

……

mFrontBufferIndex=buf;

//得到FrontBuffer对应的GraphicBuffer。

sp<GraphicBuffer>newFrontBuffer(getBuffer(buf));

if(newFrontBuffer!=NULL){

//取出脏区域。

const Region dirty(lcblk->getDirtyRegion(buf));

//和GraphicBuffer所表示的区域进行裁剪,得到一个脏区域。

mPostedDirtyRegion=dirty.intersect(newFrontBuffer->getBounds());

const Layer:State&front(drawingState());

if(newFrontBuffer->getWidth()==front.requested_w&&

newFrontBuffer->getHeight()==front.requested_h)

{

if((front.w!=front.requested_w)||

(front.h!=front.requested_h))

{

……//需要重新计算可见区域。

recomputeVisibleRegions=true;

}

mFreezeLock.clear();

}

}else{

mPostedDirtyRegion.clear();

}

if(lcblk->getQueuedCount()){

mFlinger->signalEvent();

}

/*

如果脏区域不为空,则需要绘制成纹理,reloadTexture将绘制一张纹理保存在mTextures数组中,里边涉及很多OpenGL的操作,读者有兴趣可以自己研究。*/

if(!mPostedDirtyRegion.isEmpty()){

reloadTexture(mPostedDirtyRegion);

}

}


我们知道,Layer的lockPageFlip将根据FrontBuffer的内容生成一张纹理。那么,unlockPageFlip会做些什么呢?

(2)unlockPageFlip分析

unlockPageFlip的代码如下所示:


[—>SurfaceFlinger.cpp]

void SurfaceFlinger:unlockPageFlip(const LayerVector&currentLayers)

{

const GraphicPlane&plane(graphicPlane(0));

const Transform&planeTransform(plane.transform());

size_t count=currentLayers.size();

sp<LayerBase>const*layers=currentLayers.array();

for(size_t i=0;i<count;i++){

const sp<LayerBase>&layer=layers[i];

//调用每个显示层的unlockPageFlip,Layer的unlockPageFlip主要做一些

//区域的清理工作,读者可以自己看看。

layer->unlockPageFlip(planeTransform,mDirtyRegion);

}

}


(3)关于handlePageFlip的总结

handlePageFlip的工作其实很简单,以Layer类型为例来总结一下:

各个Layer需要从FrontBuffer中取得新数据,并生成一张OpenGL中的纹理。纹理可以看做是一个图片,这个图片的内容就是FrontBuffer中的图像。

现在每一个Layer都准备好了新数据,下一步的工作当然就是绘制了。来看handleRepaint函数。

3.handleRepaint函数分析

handleRepaint函数的代码如下所示:


[—>SurfaceFlinger.cpp]

void SurfaceFlinger:handleRepaint()

{

mInvalidRegion.orSelf(mDirtyRegion);

if(mInvalidRegion.isEmpty()){

return;

}

……

const DisplayHardware&hw(graphicPlane(0).displayHardware());

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

uint32_t flags=hw.getFlags();

i f((flags&DisplayHardware:SWAP_RECTANGLE)||

(flags&DisplayHardware:BUFFER_PRESERVED))

{

……//计算mDirtyRegion。

}

//在脏区域上进行绘制。

composeSurfaces(mDirtyRegion);

mDirtyRegion.clear();

}


其中,composeSurfaces将不同的显示层内容进行混合,其实就是按Z轴的顺序由里到外依次绘制。当然,最后绘制的数据有可能遮盖前面绘制的数据,代码如下所示:


[—>SurfaceFlinger.cpp]

void SurfaceFlinger:composeSurfaces(const Region&dirty)

{

const SurfaceFlinger&flinger(*this);

const LayerVector&drawingLayers(mDrawingState.layersSortedByZ);

const size_t count=drawingLayers.size();

sp<LayerBase>const*const layers=drawingLayers.array();

for(size_t i=0;i<count;++i){

const sp<LayerBase>&layer=layers[i];

const Region&visibleRegion(layer->visibleRegionScreen);

if(!visibleRegion.isEmpty()){

const Region clip(dirty.intersect(visibleRegion));

if(!clip.isEmpty()){

layer->draw(clip);//调用各个显示层的layer函数。

}

}

}

}


draw函数在LayerBase类中实现,代码如下所示:


[—>LayerBase.cpp]

void LayerBase:draw(const Region&inClip)const

{

……

glEnable(GL_SCISSOR_TEST);

onDraw(clip);//调用子类的onDraw函数。

}


至于Layer是怎么实现这个onDraw函数的,代码如下所示:


[—>Layer.cpp]

void Layer:onDraw(const Region&clip)const

{

int index=mFrontBufferIndex;

if(mTextures[index].image==EGL_NO_IMAGE_KHR)

index=0;

GLuint textureName=mTextures[index].name;

……

Region holes(clip.subtract(under));

if(!holes.isEmpty()){

clearWithOpenGL(holes);

}

return;

}

//index是FrontBuffer对应生成的纹理,在lockPageFlip函数中就已经生成了。

drawWithOpenGL(clip,mTextures[index]);//将纹理画上去,里面有很多和OpenGL相关的内容。

}


drawWithOpenGL函数由LayerBase实现,看它是不是使用了这张纹理,代码如下所示:


[—>LayerBase.cpp]

void LayerBase:drawWithOpenGL(const Region&clip,const Texture&texture)const

{

const DisplayHardware&hw(graphicPlane(0).displayHardware());

const uint32_t fbHeight=hw.getHeight();

const State&s(drawingState());

//validateTexture函数内部将绑定指定的纹理。

validateTexture(texture.name);

//下面就是OpenGL操作函数了。

glEnable(GL_TEXTURE_2D);

……

glMatrixMode(GL_TEXTURE);

glLoadIdentity();

//坐标旋转。

switch(texture.transform){

case HAL_TRANSFORM_ROT_90:

glTranslatef(0,1,0);

glRotatef(-90,0,0,1);

break;

case HAL_TRANSFORM_ROT_180:

glTranslatef(1,1,0);

glRotatef(-180,0,0,1);

break;

case HAL_TRANSFORM_ROT_270:

glTranslatef(1,0,0);

glRotatef(-270,0,0,1);

break;

}

if(texture.NPOTAdjust){

//缩放处理。

glScalef(texture.wScale,texture.hScale,1.0 f);

}

//使能纹理坐标

glEnableClientState(GL_TEXTURE_COORD_ARRAY);

//设置顶点坐标。

glVertexPointer(2,GL_FIXED,0,mVertices);

//设置纹理坐标。

glTexCoordPointer(2,GL_FIXED,0,texCoords);

while(it!=end){

const Rect&r=*it++;

const GLint sy=fbHeight-(r.top+r.height());

//裁剪。

glScissor(r.left,sy,r.width(),r.height());

//画矩形。

glDrawArrays(GL_TRIANGLE_FAN,0,4);

}

//禁止纹理坐标。

glDisableClientState(GL_TEXTURE_COORD_ARRAY);

}


纹理绑定是OpenGL的常用函数,其代码如下所示。


[—>LayerBase.cpp]

void LayerBase:validateTexture(GLint textureName)const

{

//下面这个函数将绑定纹理。

glBindTexture(GL_TEXTURE_2D,textureName);

……//其他一些设置

}


handleRepaint这个函数基本上就是按Z轴的顺序对每一层进行重绘,重绘的方法就是使用OpenGL。

注意 我在Android平台上有几个月的OpenGL开发经历,还谈不上很深刻,不过整理了一些资料,希望能够给感兴趣的读者提供参考。

1)OpenGL的入门教材当选NeHe的资料,大略看前几章即可。

2)Android平台上关于OpenGL ES的开发,有一篇很详细的Word文档叫《OpenGL ES Tutorial for Android》。该文详细地描述了在Android平台上进行OpenGL开发的流程。大家可跟着这篇教材,在模拟器上做一些练习。那里面所涉及的一些基础知识,可从前面介绍的入门教材中学到。

3)有了前面两点的基础后,就需要对整个OpenGL有比较完整深入的了解了。我在那时所看的书是《OpenGL Programming Guide(7th Edition)》。该书很厚,有1000多页。里面有一些内容可能与工作无关,只要大概知道有那回事就行了,暂时不必深入学习,等需要时再进一步学习并运用。我在开发的项目中曾用到的光照、雾化等效果,都是之前先知道有这个东西,后来在项目中才逐渐学习运用的。

4)嵌入式平台上用的其实是OpenGL ES。这里,还有一本书叫《OpenGL ES 2.0 Programming Guide》,它介绍了OpenGL ES的开发,读者可认真学习。

5)在Android SDK文档中,对OpenGL API的描述只有寥寥数语。怎么办?不过,由于它使用了J2ME中的javax.microedition.khronos.opengles包,所以J2ME的SDK文档中对OpenGL的API有着非常详细的描述,读者手头应该要有一个J2ME的文档。

6)如果想做深入开发,就不得不学习计算机图形学了。我后来买了书,可惜没时间学了。

4.unlockClients和postFrameBuffer分析

在绘制完图后,还有两项工作需要做,一个涉及unlockClients函数,另外一个涉及postFrameBuffer函数,这两个函数分别干了什么呢?unlockClients的代码如下所示:


[—>SurfaceFlinger.cpp]

void SurfaceFlinger:unlockClients()

{

const LayerVector&drawingLayers(mDrawingState.layersSortedByZ);

const size_t count=drawingLayers.size();

sp<LayerBase>const*const layers=drawingLayers.array();

for(size_t i=0;i<count;++i){

const sp<LayerBase>&layer=layers[i];

layer->finishPageFlip();

}

}


再看Layer的finishPageFlip函数,代码如下所示:


[—>Layer.cpp]

void Layer:finishPageFlip()

{

//释放FrontBufferIndex。

status_t err=lcblk->unlock(mFrontBufferIndex);

}


原来,unlockClients会释放之前占着的FrontBuffer的索引号。下面看最后一个函数postFrameBuffer,代码如下所示:


[—>SurfaceFlinger.cpp]

void SurfaceFlinger:postFramebuffer()

{

if(!mInvalidRegion.isEmpty()){

const DisplayHardware&hw(graphicPlane(0).displayHardware());

const nsecs_t now=systemTime();

mDebugInSwapBuffers=now;

//调用这个函数后,混合后的图像就会传递到屏幕中显示了。

hw.flip(mInvalidRegion);

mLastSwapBufferTime=systemTime()-now;

mDebugInSwapBuffers=0;

mInvalidRegion.clear();

}

}


flip将调用在DisplayHardware一节中提到的eglSwapBuffer函数,以完成FrameBuffer的PageFlip操作,代码如下所示:


[—>DisplayHardware.cpp]

void DisplayHardware:flip(const Region&dirty)const

{

checkGLErrors();

EGLDisplay dpy=mDisplay;

EGLSurface surface=mSurface;

……

if(mFlags&PARTIAL_UPDATES){

mNativeWindow->setUpdateRectangle(dirty.getBounds());

}

mPageFlipCount++;

eglSwapBuffers(dpy,surface);//PageFlipping,此后图像终于显示在屏幕上了!

}