Surface,Layer,SurfaceFlinger 与BufferQueue简单记录

根据官方文档可知:
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介绍

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336