图形显示

Surface,Layer,SurfaceFlinger 与Bu

2020-05-25  本文已影响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介绍

上一篇 下一篇

猜你喜欢

热点阅读