【Android话题-6.3UI体系相关】surface的绘制原理

问题:

  • 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就是它的灵魂!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值