根据官方文档可知:
SurfaceFlinger
从WindowManager
处接收buffers
和window
相关数据。
然后SurfaceFlinger
将buffers
和window
相关数据,合成一个Layer
,发送给WindowManager
,由WindowManager
操控显示在屏幕上。fling有扔,掷,抛的意思。flinger是指扔的人。SurfaceFlinger
将Surface
扔给WindowManager
。
SurfaceFlinger
的作用就是合成Layer
。
Layer
与Surface
的关系如下:
Layer = Surface + SurfaceControl
这里其实关于surface和layer的解释并不直观。
Android opengl es
的资料远不如opengl
多(特别是在我并不想看源码的情况下)。可以参考opengl
相关书籍。很多东西找不到详细的资料解释,原因是代码很多地方并不是原创而是移植,因此只有使用类文档,原理并不详细。有时候看技术文档,一层层地减少信息量,到了后来都是精简版的信息,会对理解造成阻碍,甚至有时候会造成错误理解。
Layer
的合成过程这个过程类似于《OpenGL超级宝典》中的将绘图坐标映射到窗口,而绘图坐标,就类似于Surface
,SurfaceControl
类似下面的映射的选择,控制如何映射(例如存在2种不同的映射,具体如何映射是需要定义的,window
相关数据例如屏幕大小等在发送给SurfaceFlinger
后,还需要一个对象去控制映射),然后就是绘图坐标进行映射,这个过程相当于SurfaceFlinger 使用Surface
+ SurfaceControl
去合成 Layer
,最终显示在窗口需要的图形就是 Layer
。
出处:OpenGL超级宝典第五版第一章 3D图形和opengl简介
1.png
2.png
3.png
Surface contain BufferQueue
Surface
包含BufferQueue
。
实际上Surface
就类似opengl
中的帧缓冲区对象(FBO:FramebufferObject
)的概念。
由于译本可能会导致理解上存在一定误差,建议中英文对照去看。网上均可下载。
例如当时看这本书的时候,不太能理解缓冲区的概念,后面一看buffer
这个名词就比较好理解了。
FBO
是包含了buffer
的一个Object
,并不占用存储空间,真正占用存储空间的是buffer
,这个buffer
存储了可以渲染的数据(例如RGB
或YUV
等数据),这个Object
在Android
中定义为Surface
类,Surface
存储的buffer
是GraphicBuffer
,GraphicBuffer
存储在BufferQueue
中。如果想深入了解
GraphicBuffer
可以参考这篇:Android P 图像显示系统(二)GraphicBuffer和Gralloc分析关于
BufferQueue
可以参考:深入浅出Android BufferQueueBufferQueue分析: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的空间。
执行
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
与计算机图形学等相关书籍,简单了解计算机图形是如何到屏幕上的。
当backbuffer
经SurfaceFlinger
合成Layer
后,back和front就进行了交换,0-3显示front,4-7显示back。
当再产生新的数据时,例如0011,赋值给back。
3.png
然后再合成Layer显示后再交换。
4.png
然后再次产生新数据0111,函数中会执行赋值给back。
5.png
然后在backbuffer显示后,front和back再次进行交换。
数据变化为:0001 0010 -> 0001 0011 -> 0111 0011,这就是双缓冲的简化版流程。
使用了2个buffer,当一个空间用于合成图形时,另一个空间用于接收产生的数据,作用是改善卡顿。
但是仅仅这样还不够完善,因此还需要使用垂直同步(VSync),具体原因可参考这篇Android图形显示系统(一)
。
SurfaceFlinger
与BufferQueue
的关系:SurfaceFlinger
将buffer
和window
相关数据,合成一个Layer
,而这个buffer
是SurfaceFlinger
从BufferQueue
获取(调用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介绍
网友评论