Android 图形架构 之六——深入分析draw()是如何工作的

Posted 薛瑄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 图形架构 之六——深入分析draw()是如何工作的相关的知识,希望对你有一定的参考价值。

前言

前面我们分析了 app进程中的 addView 的执行过程,其中涉及到了ViewRootImpl,WMS,Session,WindowState,Surface 等等,最后分析到performDraw() 函数,接下来,我们就继续来分析,带着两个问题,它是如何把数据写入生产者队列的,已经SurfaceFlinger 何时开始处理的?

Android 图形架构 之一 ——概述
Android 图形架构 之二—— SurfaceFlinger 启动和连接
Android 图形架构 之三—— 创建Layer、Surface、SurfaceControl
Android 图形架构 之四——图形缓冲区的申请和消费流程及核心类
Android 图形架构 之五——深入分析addView所发生的的一切
Android 图形架构 之六——深入分析draw()是如何工作的
Android 图形架构 之七——Choreographer 源码分析
android图形架构 之八——硬件VSync、VSync-app、Vsync-sf

流程分析

本文要分析的主流程,如下图所示:

接下来就从performDraw 开始分析:

文件路径:frameworks/base/core/java/android/view/ViewRootImpl.java

代码一:
   private void performDraw() 
       ......
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try 
            draw(fullRedrawNeeded);
         finally 
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        
        ......
    

    private void draw(boolean fullRedrawNeeded) 

     .....
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) 
           return;
        
     ......
    
代码二:
    /**
     * @return true if drawing was successful, false if an error occurred
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) 

        // Draw with software renderer.

          .....
          //申请的图形缓冲区,并锁定,用于在上面写数据,代码三分析
         canvas = mSurface.lockCanvas(dirty);
         .....
         //调用系统的api(这里指Canvas 类的 ),向缓冲区写入数据,这里也会调用我们熟悉的onDraw 函数
         mView.draw(canvas);
         .....
         //解锁缓冲区,入队,并通知SurfaceFlinger 去消费,下面会分析
         surface.unlockCanvasAndPost(canvas);
         .....

        return true;
    

lockCanvas

来看看mSurface.lockCanvas(dirty);

位于 frameworks/base/core/java/android/view/Surface.java:

代码三:
    public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException 
        synchronized (mLock) 
            checkNotReleasedLocked();

            //调用Native代码
            //mCanvas 里面有SkBitmap,SkBitmap里面的数据 指向了缓冲区队列中 某一个BufferSlot 对应的 GraphicBuffer
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        
    

位于 frameworks/base/core/jni/android_view_Surface.cpp:

代码四:
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) 
     //获取java层的Surface保存的long型句柄
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    ......

    Rect dirtyRect(Rect::EMPTY_RECT);
    Rect* dirtyRectPtr = NULL;
    //获取java层dirty Rect的位置大小信息
    if (dirtyRectObj) 
        dirtyRect.left   = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirtyRect.top    = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirtyRect.right  = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        dirtyRectPtr = &dirtyRect;
    

    ANativeWindow_Buffer outBuffer;
    //调用Surface的lock方法,获取一个缓冲区,并赋值给outBuffer
    //代码五分析
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);
    ......
    //创建一个SkImageInfo ,用于下面创建SkBitmap 
    SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
                                         convertPixelFormat(outBuffer.format),
                                         outBuffer.format == PIXEL_FORMAT_RGBX_8888
                                                 ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
   // 使用outBuffer为SkBitmap变量赋值
    SkBitmap bitmap;
    //获取图形缓冲区每一行像素大小
    ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
    bitmap.setInfo(info, bpr);
    if (outBuffer.width > 0 && outBuffer.height > 0) 
        //把outBuffer的数据,设置到bitmap中
        bitmap.setPixels(outBuffer.bits);
     else 
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    

    
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    //把刚才创建的SkBitmap ,设置到Canvas 中,后续 就可以在这个bitmap上绘图了
    nativeCanvas->setBitmap(bitmap);

    if (dirtyRectPtr) 
       //剪裁出dirty区域大小
        nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top,
                dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect);
    

    if (dirtyRectObj) 
        //将剪裁位置大小信息赋给java层Canvas对象
        env->SetIntField(dirtyRectObj, gRectClassInfo.left,   dirtyRect.left);
        env->SetIntField(dirtyRectObj, gRectClassInfo.top,    dirtyRect.top);
        env->SetIntField(dirtyRectObj, gRectClassInfo.right,  dirtyRect.right);
        env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
    

    // Create another reference to the surface and return it.  This reference
    // should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
    // because the latter could be replaced while the surface is locked.
    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();

接下来分析Surface的lock函数,它的功能是申请图形缓冲区,并锁定这块缓冲区,后面就可调用draw函数,来绘制了
函数位于frameworks/native/libs/gui/Surface.cpp:

代码五:
status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)

    if (mLockedBuffer != nullptr) 
        ALOGE("Surface::lock failed, already locked");
        return INVALID_OPERATION;
    

    if (!mConnectedToCpu) 
        // 调用connect函数, 连接cpu
        int err = Surface::connect(NATIVE_WINDOW_API_CPU);
        if (err) 
            return err;
        
        // we're intending to do software rendering from this point
        setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
    

    ANativeWindowBuffer* out;
    int fenceFd = -1;
    //调用dequeueBuffer函数,申请图形缓冲区,代码六会分析
    status_t err = dequeueBuffer(&out, &fenceFd);
    ALOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err));
    if (err == NO_ERROR) 
        //获取图形缓冲区区域大小,赋给后备缓冲区变量backBuffer
        sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
        //该缓冲区的宽高,赋值给bounds
        const Rect bounds(backBuffer->width, backBuffer->height);

        Region newDirtyRegion;
        //是否在脏区域边界,newDirtyRegion表示新的脏区域
        if (inOutDirtyBounds) 
            //设置新的脏区域 
            newDirtyRegion.set(static_cast<Rect const&>(*inOutDirtyBounds));
            //脏区域和 缓冲区区域 作 与运算,求交集
            newDirtyRegion.andSelf(bounds);
         else 
            //不是只刷新脏区域,则待刷新区域,是整个缓冲区的宽高
            newDirtyRegion.set(bounds);
        

        // figure out if we can copy the frontbuffer back
        //上一次绘制的信息保存在mPostedBuffer中,而这个mPostedBuffer则要在unLockAndPost函数中设置
        const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
        //比较 本次 和 上次的缓冲区,宽、高、格式是否一致
        const bool canCopyBack = (frontBuffer != nullptr &&
                backBuffer->width  == frontBuffer->width &&
                backBuffer->height == frontBuffer->height &&
                backBuffer->format == frontBuffer->format);

        if (canCopyBack) 
            // copy the area that is invalid and not repainted this round
            // 获取到需要更新的脏区域
            const Region copyback(mDirtyRegion.subtract(newDirtyRegion));
            if (!copyback.isEmpty()) 
                //把frontBuffer 数据拷贝到backBuffer,后续只用更新该脏区域的数据,提高了效率
                copyBlt(backBuffer, frontBuffer, copyback, &fenceFd);
            
         else 
            // if we can't copy-back anything, modify the user's dirty
            // region to make sure they redraw the whole buffer
            //如果两次缓冲区的大小,格式不一致,则设置脏区域为当前缓冲区的大小 ,后续更新整个缓冲区
            newDirtyRegion.set(bounds);
            mDirtyRegion.clear();
            Mutex::Autolock lock(mMutex);
            for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) 
                mSlots[i].dirtyRegion.clear();
            
        


         // scope for the lock
            Mutex::Autolock lock(mMutex);
            //得到此次后备缓冲器的bufferSlot在mSlots里面的index
            int backBufferSlot(getSlotFromBufferLocked(backBuffer.get()));
            if (backBufferSlot >= 0) 
                //将新的dirty赋给这个bufferslot
                Region& dirtyRegion(mSlots[backBufferSlot].dirtyRegion);
                mDirtyRegion.subtract(dirtyRegion);
                dirtyRegion = newDirtyRegion;
            
        
		//两个脏区域,或运算,相叠加
        mDirtyRegion.orSelf(newDirtyRegion);
        if (inOutDirtyBounds) 
            *inOutDirtyBounds = newDirtyRegion.getBounds();
        

        void* vaddr;
        //lock和unlock分别用来锁定和解锁一个指定的图形缓冲区,在访问一块图形缓冲区的时候,
        //例如,向一块图形缓冲写入内容的时候,需要将该图形缓冲区锁定,用来避免访问冲突,
        //锁定之后,就可以获得由参数参数l、t、w和h所圈定的一块缓冲区的起始地址,保存在输出参数vaddr中
        status_t res = backBuffer->lockAsync(
                GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                newDirtyRegion.bounds(), &vaddr, fenceFd);

        ALOGW_IF(res, "failed locking buffer (handle = %p)",
                backBuffer->handle);

        if (res != 0) 
            err = INVALID_OPERATION;
         else 
            //mLockedBuffer 表示锁定的buffer,指向当前绘制的buffer backBuffer
            mLockedBuffer = backBuffer;
            //outBuffer 是要返回的数据,使用backBuffer 为它赋值
            outBuffer->width  = backBuffer->width;
            outBuffer->height = backBuffer->height;
            outBuffer->stride = backBuffer->stride;
            outBuffer->format = backBuffer->format;
            //指向一块缓冲区的起始地址,这块缓冲区被锁定了,接下来就可以在上面写入数据
            outBuffer->bits   = vaddr;
        
    
    return err;

注意在Surface 中也有一个结构体BufferSlot,代码如下,它和BufferQueueCore中的BufferSlot 类型有点差别。前者是在app进程中,后者是在SurfaceFlinger进程中

    struct BufferSlot 
        sp<GraphicBuffer> buffer;
        Region dirtyRegion;
    ;

接下来 调用了Surface::dequeueBuffer

代码六:
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) 
    ......

    FrameEventHistoryDelta frameTimestamps;
    //在生产者-消费者队列,申请图形缓冲区
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
                                                            reqFormat, reqUsage, &mBufferAge,
                                                            enableFrameTimestamps ? &frameTimestamps
                                                                                  : nullptr);

    .....
    if (buf < 0 || buf >= NUM_BUFFER_SLOTS) 
        ALOGE("dequeueBuffer: IGraphicBufferProducer returned invalid slot number %d", buf);
        android_errorWriteLog(0x534e4554, "36991414"); // SafetyNet logging
        return FAILED_TRANSACTION;
    

    Mutex::Autolock lock(mMutex);

    // Write this while holding the mutex
    mLastDequeueStartTime = startTime;
	//这里的mSlots,是Surface中的BufferSlot数组,其中buffer 类型是GraphicBuffer 
    sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);

    if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) 
        static FenceMonitor hwcReleaseThread("HWC release");
        hwcReleaseThread.queueFence(fence);
    
    //如果所有状态buffer的标志位是RELEASE_ALL_BUFFERS,表示个数大于64,就要释放所有超过64的buffer
    if (result & IGraphicBufferProducer::RELEASE_ALL_BUFFERS) 
        freeAllBuffers();
    

    if (enableFrameTimestamps) 
         mFrameEventHistory->applyDelta(frameTimestamps);
    
    //新分配的BufferSlot的状态是BUFFER_NEEDS_REALLOCATION标志,代表需要重新分配了GraphicBuffer
    if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == nullptr) 
        if (mReportRemovedBuffers && (gbuf != nullptr)) 
            mRemovedBuffers.push_back(gbuf);
        
        //由于申请内存的操作是在surfaceflinger进程中,当前在app进程,所以这是一个进程间通信
        //buf 存储的是BufferSlot在mSlot 的索引,gbuf 存储是GraphicBuffer
        //把buf 对应的BufferSlot 对应的GraphicBuffer,指针赋值给gbuf
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
        .....
    

    ......
    //获取这个buffer对象的指针内容
    *buffer = gbuf.get();

    if (mSharedBufferMode && mAutoRefresh) 
        mSharedBufferSlot = buf;
        mSharedBufferHasBeenQueued = false;
     else if (mSharedBufferSlot == buf) 
        mSharedBufferSlot = BufferItem::INVALID_BUFFER_SLOT;
        mSharedBufferHasBeenQueued = false;
    

    return OK;

调用BufferQueueProducer的dequeueBuffer函数申请一块图形缓冲区,这个在文章 Android 图形架构 之四——图形缓冲区的申请和消费流程及核心类 分析过了

这里分析一下BufferQueueProducer的requestBuffer,它是通过IPC Binder调用远程接口,(Binder 通信的过程就不分析了)直接看实现,位于frameworks/native/libs/gui/BufferQueueProducer.cpp:

在SurfaceFlinger 创建Layer 的时候,调用createBufferQueue,进而创建了BufferQueueProducer,这些操作是在SurfaceFlinger 进程中的 ,可阅读Android 图形架构 之三—— 创建Layer、Surface、SurfaceControlAndroid 图形架构 之四——图形缓冲区的申请和消费流程及核心类 来了解这些

该函数 是根据指定index取出mSlots中的slot中的buffer。

代码七:
status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) 
    ATRACE_CALL();
    BQ_LOGV("requestBuffer: slot %d", slot);
    Mutex::Autolock lock(mCore->mMutex);

    if (mCore->mIsAbandoned) 
        BQ_LOGE("requestBuffer: BufferQueue has been abandoned");
        return NO_INIT;
    
    //check index and state
    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) 
        BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)",
                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
        return BAD_VALUE;
     else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) 
        BQ_LOGE("requestBuffer: slot %d is not owned by the producer "
                "(state = %d)", slot, mSlots[slot].mBufferState);
        return BAD_VALUE;
    
    //注意,这里已经到了SurfaceFlinger进程中,此时的BufferSlot的Graphicbuffer 数据存储在变量 mGraphicBuffer
    //将slot对应的BufferSlot  mRequestBufferCalled 标志位置为true,并返回BufferSlot的Graphicbuffer
    mSlots[slot].mRequestBufferCalled = true;
    *buf = mSlots[slot].mGraphicBuffer;
    return NO_ERROR;

draw

接着回到代码二,mView.draw(canvas); ,调用Canvas 的api 在这块缓冲区上,进行数据的绘制。此处我们就省略了,下面看看SurfaceFlinger 如何使用这块缓冲区

unlockCanvasAndPost

接下来 在代码二中,调用了 surface.unlockCanvasAndPost(canvas);,主要作用就是把缓冲区入队,通知SurfaceFlinger进行渲染
代码路径:frameworks/base/core/java/android/view/Surface.java

代码八:
    public void unlockCanvasAndPost(Canvas canvas) 
        synchronized (mLock) 
            checkNotReleasedLocked();

            if (mHwuiContext != null) 
                mHwuiContext.unlockAndPost(canvas);
             else 
                unlockSwCanvasAndPost(canvas);
            
        
    

    private void unlockSwCanvasAndPost(Canvas canvas) 
       .以上是关于Android 图形架构 之六——深入分析draw()是如何工作的的主要内容,如果未能解决你的问题,请参考以下文章