美文网首页图形显示
Surface,Layer,SurfaceFlinger 与Bu

Surface,Layer,SurfaceFlinger 与Bu

作者: 梧叶已秋声 | 来源:发表于2020-05-25 11:23 被阅读0次

    根据官方文档可知:
    SurfaceFlingerWindowManager处接收bufferswindow 相关数据。
    然后SurfaceFlingerbufferswindow相关数据,合成一个Layer,发送给WindowManager,由WindowManager操控显示在屏幕上。fling有扔,掷,抛的意思。flinger是指扔的人。SurfaceFlingerSurface扔给WindowManager

    SurfaceFlinger的作用就是合成Layer
    LayerSurface的关系如下:
    Layer = Surface + SurfaceControl
    这里其实关于surface和layer的解释并不直观。
    Android opengl es的资料远不如opengl多(特别是在我并不想看源码的情况下)。可以参考opengl相关书籍。很多东西找不到详细的资料解释,原因是代码很多地方并不是原创而是移植,因此只有使用类文档,原理并不详细。有时候看技术文档,一层层地减少信息量,到了后来都是精简版的信息,会对理解造成阻碍,甚至有时候会造成错误理解。
    Layer的合成过程这个过程类似于《OpenGL超级宝典》中的将绘图坐标映射到窗口,而绘图坐标,就类似于SurfaceSurfaceControl类似下面的映射的选择,控制如何映射(例如存在2种不同的映射,具体如何映射是需要定义的,window 相关数据例如屏幕大小等在发送给SurfaceFlinger 后,还需要一个对象去控制映射),然后就是绘图坐标进行映射,这个过程相当于SurfaceFlinger 使用Surface+ SurfaceControl去合成 Layer,最终显示在窗口需要的图形就是 Layer

    出处:OpenGL超级宝典第五版第一章 3D图形和opengl简介


    1.png
    2.png
    3.png

    Surface contain BufferQueue

    Surface 包含BufferQueue

    image.png

    实际上Surface 就类似opengl中的帧缓冲区对象(FBO:FramebufferObject)的概念。
    由于译本可能会导致理解上存在一定误差,建议中英文对照去看。网上均可下载。
    例如当时看这本书的时候,不太能理解缓冲区的概念,后面一看buffer这个名词就比较好理解了。

    OpenGL超级宝典第五版
    FBO是包含了buffer的一个Object,并不占用存储空间,真正占用存储空间的是buffer,这个buffer存储了可以渲染的数据(例如RGBYUV等数据),这个ObjectAndroid中定义为Surface类,Surface存储的bufferGraphicBuffer,GraphicBuffer存储在BufferQueue中。
    如果想深入了解GraphicBuffer可以参考这篇:Android P 图像显示系统(二)GraphicBuffer和Gralloc分析
    关于BufferQueue可以参考:深入浅出Android BufferQueue
    BufferQueue分析:Buffer队列

    我并没有怎么看懂,粗略看了下,C++看的我头痛。回想起来跟之前的一个做c++的同事联调代码的时候对方表示java看得也很头痛。语言有时候真的是很大障碍,至少对我来说是这样。

    出处:https://source.android.com/devices/graphics/arch-bq-gralloc
    使用方创建并拥有 BufferQueue 数据结构,并且可存在于与其生产方不同的进程中。当生产方需要缓冲区时,它会通过调用 dequeueBuffer() 从 BufferQueue 请求一个可用的缓冲区,并指定缓冲区的宽度、高度、像素格式和使用标记。然后,生产方填充缓冲区并通过调用 queueBuffer() 将缓冲区返回到队列。接下来,使用方通过 acquireBuffer() 获取该缓冲区并使用该缓冲区的内容。当使用方操作完成后,它会通过调用 releaseBuffer() 将该缓冲区返回到队列。同步框架可控制缓冲区在 Android 图形管道中移动的方式。
    BufferQueue 的一些特性(例如可以容纳的最大缓冲区数)由生产方和使用方联合决定。但是,BufferQueue 会根据需要分配缓冲区。除非特性发生变化,否则将会保留缓冲区;例如,如果生产方请求具有不同大小的缓冲区,则系统会释放旧的缓冲区,并根据需要分配新的缓冲区。
    BufferQueue 永远不会复制缓冲区内容,因为移动如此多的数据是非常低效的操作。相反,缓冲区始终通过句柄进行传递。

    出处:https://source.android.com/devices/graphics/arch-sh
    用于显示 Surface 的 BufferQueue 通常配置为三重缓冲。缓冲区是按需分配的,因此,如果生产方足够缓慢地生成缓冲区(例如在 60 fps 的显示屏上以 30 fps 的速度进行缓冲),队列中可能只有两个分配的缓冲区。按需分配缓冲区有助于最大限度地减少内存消耗。您可以看到与 dumpsys SurfaceFlinger 输出中每个层级相关的缓冲区的摘要。

    简单来说就是:Buffer队列中存在一个或多个buffer(按需分配,通常是三重缓冲),所谓3重缓冲,就是使用3个buffer去存储和处理数据,2重缓冲就是使用2个buffer。BufferQueue通过handle进行传递buffer中的内容而不是copy(类似于handle+MessageQueue)。

    下面来看看surface的双缓冲。

    //frameworks\native\libs\gui\Surface.cpp
    status_t Surface::lock(
            ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
    {
        if (mLockedBuffer != 0) {
            ALOGE("Surface::lock failed, already locked");
            return INVALID_OPERATION;
        }
    
        if (!mConnectedToCpu) {
            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;
        status_t err = dequeueBuffer(&out, &fenceFd);
        ALOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err));
        if (err == NO_ERROR) {
            sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
            const Rect bounds(backBuffer->width, backBuffer->height);
    
            Region 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
            const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
            const bool canCopyBack = (frontBuffer != 0 &&
                    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()) {
                    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);
                int backBufferSlot(getSlotFromBufferLocked(backBuffer.get()));
                if (backBufferSlot >= 0) {
                    Region& dirtyRegion(mSlots[backBufferSlot].dirtyRegion);
                    mDirtyRegion.subtract(dirtyRegion);
                    dirtyRegion = newDirtyRegion;
                }
            }
    
            mDirtyRegion.orSelf(newDirtyRegion);
            if (inOutDirtyBounds) {
                *inOutDirtyBounds = newDirtyRegion.getBounds();
            }
    
            void* 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 = backBuffer;
                outBuffer->width  = backBuffer->width;
                outBuffer->height = backBuffer->height;
                outBuffer->stride = backBuffer->stride;
                outBuffer->format = backBuffer->format;
                outBuffer->bits   = vaddr;
            }
        }
        return err;
    }
    

    以下出处:显示缓冲区的作用

    status_t Surface::lock(SurfaceInfo* other, Region* dirtyIn, bool blocking) 
    {
        ......
        android_native_buffer_t* out;
        // 分配新的内存空间并将其加入缓冲队列,返回给out
        status_t err = dequeueBuffer(&out);
        if (err == NO_ERROR) {
            // 从刚才得到的buffer创建GraphicBuffer对象,
            // 该对象是用来更新显示的缓冲区,叫做背景缓冲区。
            // 重画动作在背景缓冲区进行。
            sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
            // 锁定这片内存
            err = lockBuffer(backBuffer.get());
            if (err == NO_ERROR) {
                const Rect bounds(backBuffer->width, backBuffer->height);
                const Region boundsRegion(bounds);
                Region scratch(boundsRegion);
                // newDirtyRegion是需要重画的区域
                Region& newDirtyRegion(dirtyIn ? *dirtyIn : scratch);
                newDirtyRegion &= boundsRegion;
     
                // 已经显示出来的frontBuffer叫做前景缓冲区
                // 判断是否需要拷贝frontBuffer到backBuffer
                const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
                const bool canCopyBack = (frontBuffer != 0 &&
                        backBuffer->width  == frontBuffer->width &&
                        backBuffer->height == frontBuffer->height &&
                        backBuffer->format == frontBuffer->format &&
                        !(mFlags & ISurfaceComposer::eDestroyBackbuffer));
     
                mDirtyRegion = newDirtyRegion;
     
                // 如果需要做拷贝动作,则将frontBuffer中非newDirtyRegion区域
                // 拷贝到backBuffer中
                if (canCopyBack) {
                    const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion));
                    if (!copyback.isEmpty())
                        copyBlt(backBuffer, frontBuffer, copyback);
                } else {
                    // 如果不需要拷贝,则重画整个区域
                    newDirtyRegion = boundsRegion;
                }
     
                mOldDirtyRegion = newDirtyRegion;
     
                // 锁定将要画图的缓冲区,并返回一个地址给调用者
                void* vaddr;
                status_t res = backBuffer->lock(
                        GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                        newDirtyRegion.bounds(), &vaddr);
                 
                // 返回给SurfaceInfo参数other
                mLockedBuffer = backBuffer;
                other->w      = backBuffer->width;
                other->h      = backBuffer->height;
                other->s      = backBuffer->stride;
                other->usage  = backBuffer->usage;
                other->format = backBuffer->format;
                other->bits   = vaddr;
            }
        }
        mApiLock.unlock();
        return err;
    }
    

    从注释中大致可以了解lock函数中做了些什么操作。surface的双缓冲,关键在于Surface.cpp中的lock函数中的操作。
    用文字表述流程可以参考下面,这就是双缓冲的具体过程。可以对照代码多看几遍。


    https://wenku.baidu.com/view/1ff720b565ce050876321395.html

    前景就是已经显示的,后景是未显示的。如果一个buffer已显示,那么它就是frontBuffer ,如果未显示那就是backBuffer,frontBuffer 和backBuffer在使用过程中是会翻转的。

    出处:SurfaceView 的双缓冲
    系统先从 buffer 池中 dequeueBuffer 出来一个可用的 out,然后将 out 赋给 backBuffer。mPostedBuffer 为已经显示的 buffer,将 mPostedBuffer 的内容赋给 frontBuffer

    例如,存在一个地址为0-8的空间。

    0.png
    执行lock函数过程中,假设此时显示的buffer数据为0010,待显示的数据为0001。如下所示。即backBuffer为0001,frontBuffer为0010。此时为0-3显示front,4-7显示back。这里的数据是随便写的。实际数据是使用Canvas、OpenGL ES 或 Vulkan等去生成。

    出处:https://source.android.com/devices/graphics#image_stream_producers
    应用开发者可通过三种方式将图像绘制到屏幕上:使用 Canvas、OpenGL ES 或 Vulkan。无论开发者使用什么渲染 API,一切内容都会渲染到Surface

    一般使用Activity显示View是通过Canvas去产生图像数据。
    可参考这篇:探究Android View 绘制流程,Canvas 的由来
    View的绘制过程中会调用这句生成canvas。

    final DisplayListCanvas canvas = renderNode.start(width, height);
    

    然后会进入c++的领域,最后应该会进入BufferQueue,供surface.cpp调用。大致流程就这样。
    关于渲染等概念,建议通读OpenGL与计算机图形学等相关书籍,简单了解计算机图形是如何到屏幕上的。

    1.png

    backbufferSurfaceFlinger合成Layer后,back和front就进行了交换,0-3显示front,4-7显示back。

    2.png

    当再产生新的数据时,例如0011,赋值给back。


    3.png

    然后再合成Layer显示后再交换。


    4.png

    然后再次产生新数据0111,函数中会执行赋值给back。


    5.png

    然后在backbuffer显示后,front和back再次进行交换。

    6.png
    数据变化为:0001 0010 -> 0001 0011 -> 0111 0011,这就是双缓冲的简化版流程。
    使用了2个buffer,当一个空间用于合成图形时,另一个空间用于接收产生的数据,作用是改善卡顿。
    但是仅仅这样还不够完善,因此还需要使用垂直同步(VSync),具体原因可参考这篇Android图形显示系统(一)

    SurfaceFlingerBufferQueue的关系:SurfaceFlingerbufferwindow相关数据,合成一个Layer,而这个bufferSurfaceFlingerBufferQueue获取(调用dequeueBuffer获取buffer),然后放到backBuffer中的backBuffer。

    简化版关系图

    后记:这篇文章虽然字数不多,但是其实花了好几天才写完,看一篇文章虽然很快,但是真正理解有时候并没有没有那么容易。部分知识点其实还是存疑,例如具体运行过程,BufferQueue的运行原理等。但是由于这篇文的初衷是surface的双缓冲,以及surface到底是什么,并且由于c++的代码我很难深入看下去,因此没有深入下去。最后,由于本人知识局限性,内容可能会存在错误,如果发现了,希望能指出,防止误导他人。

    参考链接:
    AndroidO 下图形显示框架变化介绍
    https://source.android.com/devices/graphics/arch-sh
    SurfaceView 的双缓冲
    深入浅出Android BufferQueue
    深入理解Android:卷1_8.4.5 lockCanvas和unlockCanvasAndPost分析
    SurfaceView的双缓冲机制
    显示缓冲区的作用
    Android_GDI基本框架and Surface Flinger
    队列
    Android图形显示系统(一)
    Android graphics 学习-生产者、消费者、BufferQueue介绍

    相关文章

      网友评论

        本文标题:Surface,Layer,SurfaceFlinger 与Bu

        本文链接:https://www.haomeiwen.com/subject/zadlahtx.html