问题:
- surface绘制的buffer是怎么来的?
- buffer绘制完了又是怎么提交的?
surface本身不是buffer,surface跨进程传递的时候也没有带什么buffer,但是Surface绘制的时候是有buffer的,这个buffer是怎么来的呢?
绘制是在应用端本地完成的,但绘制完的结果需要提交到SurfaceFlinger才行,SurfaceFlinger来对图像进行合成并显示,因此中间需要有一个提交过程。
原理
从View的绘制开始
private void performTraversals(){
...
performMeasure(childWidthMeasureSpec, childHeightMesasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//将调用用draw函数
performDraw();
...
}
void draw(boolean fullRedrawNeeded){
Surface surface = mSurface;
...
//如果开了硬件加速就会用硬件去绘制,但默认采用软件绘制
drawSoftware(surface, mAttachInfo, ..., dirty);
...
}
private boolean drawSoftware(Surface surface, ...) {
final Canvas canvas;
...
//先拿到并锁定一块canvas
canvas = mSurface.lockCanvas(dirty);
...
//然后draw
mView.draw(canvas);
...
//解锁canvas并提交
surface.unlockCanvasAndPost(canvas);
}
public Canvas lockCanvas(Rect inOutDirty){
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
public Canvas lockCanvas(Rect inOutDirty){
//mNativeObject:是surface在native层的Surface对象的指针
//mCanvas:surface中的变量,不过现在它是空的。对于canvas来说,最重要的是要用一个bitMap
//这里说的bitMap不是我们平常说的BitMap,而是skia core中的一个数据结构————叫SkBitmap
//skBitmap就是用来给canvas做为绘制缓冲区的,调用这个函数前mCanvas为空
//调用之后mCanvas里面将被填充,然后就可以绘制了
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
native层:
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, jlong nativeObject, jobject canvasObj, jobject dirtyRectObj){
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
//先通过Native层的Surface对象获取一块Buffer
ANativeWindow_Buffer outBuffer;
surface->lock(&outBuffer, dirtyRectPtr);
//把这块buffer作为bitmap的buffer
SkBitmap bitmap;
bitmap.setPixels(outBuffer.bits);
//这样canvas就有了一块有效的bitmap,就可以绘制了
Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
nativeCanvas->setBitmap(bitmap);
sp<Surface> lockedSurface(surface);
return (jlong)lockedSurface.get();
}
- 重点是如何申请到buffer——每次绘制都要重新申请buffer
如何申请到buffer
status_t Surface::lock(ANativeWindow_Buffer* outBuffer, ...){
//先通过dequeueBuffer获得一个ANativeWindowBuffer对象
ANativeWindowBuffer* out;
dequeueBuffer(&out, &fenceFd);
//然后转换成GraphicBuffer类型
//其实dequeueBuffer返回的本身就是GraphicBuffer类型的数据
//只不过GraphicBuffer是继承了ANativeWindowBuffer
//backBuffer是什么意思?见下面描述
sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
...
void* vaddr;
//锁定buffer并获得这个buffer的内存地址,这个地址之后是要交给canvas的bitmap的
status_t res = backBuffer->lockAsync(..., &vaddr, fenceFd);
//mLockedBuffer就是Surface里表示后台buffer的变量
//前台buffer叫mPostedBuffer
mLockedBuffer = backBuffer;
outBuffer->bits = vaddr;
}
- Surface里是有两个Buffer的,一个作为前台Buffer另一个作为后台Buffer
- 前台Buffer主要用来显示,后台Buffer是用来绘制,两个Buffer互不影响
- 把后台Buffer绘制完成之后就可以切到前台去显示了
- 如果下次需要再次绘制就重新申请一个后台Buffer
- dequeueBuffer获得的buffer就是用来绘制的,因此把它叫做backBuffer
dequeBuffer的实现
int Surface::dequeueBuffer(android_native_buffer_t** buffer, ...){
int buf = -1;
//mGraphicBufferProducer是Surface的核心,下面这个调用是从远端的Buffer Slot里找一个空闲的Slot并返回它的index
//Slots是数组的意思,slot就是批数组中的元素
status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, ...);
//Surface本地有一个mSlots,远端的BufferQueue里面也有一个mSlots,这就涉及到两边同步的问题
//如果远端的BufferQueue里面某一个slot重新分配过的话,本地的mSlot也要及时更新
//如何得知远端的Buffer更新呢,就是通过上前的dequeueBuffer()调用的返回值
sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);
//如果返回值中带了BUFFER_NEEDS_REALLOCATION这个标志,则表示远端的buffer更新过了
if((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuff == 0) {
//本地的buffer也要及时刷新一下
//参数buf为前面获得的空闲的slot的下标,远端将把这个下标对应的GraphicBuffer跨进程传递到本地(也就是应用端)
//然后应用端再把它保存起来。
result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
}
*buffer = gbuf.get();
return OK;
}
GraphiceBuffer为什么可以跨进程传递呢?其实有点类似文件描述符从一个进程传递到另一个进程,然后再把它映射到进程的内在空间,这样两个进程里的buffer都可以指向同一块物理内存了,buffer本身其实是没有跨进程传递的,传递的只是一个描述符或者是类似句柄的东西。
Buffer是怎么提交的
public void unlockCanvasAndPost(Canvas canvas){
syncronized(mLock){
unlockSwCanvasAndPost(canvas);
}
}
private void unlockSwCanvasAndPost(Canvas canvas){
nativeUnlockCanvasAndPost(mLockedObject, canvas);
nativeRelease(mLockedObject);
mLockedObject = 0;
}
native层:
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz, jlong nativeObject, jobject canvasObj){
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
//给canvas设置了一个空的bitmap
nativeCanvas->setBitmap(SkBitmap());
//提交
surface->unlockAndPost();
}
status_t Surface::unlockAndPost() {
//首先把GraphicBuffer解除锁定
mLockedBuffer->unlockAsync(...);
//把buffer重新提交到bufferQueue,如何提交见下面的展开
queueBuffer(mLockedBuffer.get(), ...);
//后台buffer升级为前台buffer!
mPostedBuffer = mLockedBuffer;
//后台buffer清空!
mLockedBuffer = 0;
return err;
}
int Surface::queueBuffer(android_native_buffer_t* buffer, ...){
//先拿到这个buffer对应的index
int i = getSlotFromBufferLocked(buffer);
...
//然后通过queueBuffer()把index告诉远端,
//这样远端就知道这个buffer是谁了,然后再对这个buffer做一些处理,
//最后通知这个buffer的消息者————去回调它的onFrameAvaliable()函数
mGraphicBufferProducer->queueBuffer(i, ...);
}
- 关于GraphicBufferQueue会单独开篇文章来讲,这里只是粗略介绍一下原理
- 关于在Canvas上作图,底层是直接用了skia引擎,在此不深入研究
换一个姿势看看
- 应用里要绘制图像首先创建了一个Surface,要在Surface上绘制首先要需要Buffer
- Buffer哪里来?先要在SurfaceFlinger进程中创建一个BufferQueue——一个Surface对应一个BufferQueue
- 这个BufferQueue有一个producer端和consumer端
- producer端是要跨进程传回到应用进程的——交给Surface保管
- Surface需要绘制的时候就用producer端通过binder调用从bufferQueue申请一块buffer,这块buffer是作为canvas的缓冲区
- canvas绘制完之后再把buffer返回给BuferQueue
- BufferQueue就会去通知它的Consumer端去回调它的onFrameAvailable(),这个回调表示:又有一帧数据画好了
- Consumer端既可以是在SurfaceFlinger进程中,也可以传给别的应用进程,反正Consumer就是用来消费这一帧数据的。
总结
- surface绘制的buffer是怎么来的?
a)通过GBP向BufferQueue申请的 - buffer绘制完了又是怎么提交的?
a)通过GBP向BufferQueue提交的
PS:对于Surface来说,GBP就是它的灵魂!