明白了 fence 的基本原理,我们可以进一步的探索整个 SurfaceFlinger 的中 fence 在其中处于什么角色。
一、流转状态
从启动到屏幕的第一帧的渲染,fence 是不会有任何效果的。因为此时 fence 还没有经过 hwc_set 给 fence 进行赋值。但是到了第二帧开始,已经存在的 Layer 已经经过了 hwc_set 的赋值,存在 Layer 的 releaseFence 中。
SurfaceFlinger 中核心的 4 个流程:
- dequeueBuffer:GraphicBuffer 的出队。
- queueBuffer:GraphicBuffer 的入队。
- updateTexImage:GraphicBuffer 的消费。
- releaseBufferLocked:GraphicBuffer 的释放
下面我们看一下 Fence 在上面几个流程中参与的角色。
1、dequeueBuffer
我们先来看看 Surface 中的 lock 方法,这个方法是 onDraw 方法之前,ViewRootImpl 绘制之前进行调用。这个方法最终会调用 IGraphicBufferProducer 的 dequeue 方法。
Surface::lock
源码位置:/frameworks/native/libs/gui/Surface.cpp
status_t Surface::lock(ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
……
ANativeWindowBuffer* out;
int fenceFd = -1;
status_t err = dequeueBuffer(&out, &fenceFd);
……
if (err == NO_ERROR) {
……
void* vaddr;
status_t res = backBuffer->lockAsync(GRALLOC_USAGE_SW_READ_OFTEN
| GRALLOC_USAGE_SW_WRITE_OFTEN, newDirtyRegion.bounds(), &vaddr, fenceFd);
……
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;
}
这里 fence 参与的步骤有2个:
- 调用 dequeueBuffer() 函数,最终会调用到 BufferQueueProducer 的对应函数。
- 调用 GraphicBuffer 的 lockAsync() 进行本进程对 ion 同一段 page 进行映射的时候,也会进行 fence 的等待。
dequeueBuffer
源码位置:/frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
uint32_t width, uint32_t height, PixelFormat format, uint64_t usage, uint64_t* outBufferAge,
FrameEventHistoryDelta* outTimestamps) {
ATRACE_CALL();
{ // Autolock scope
std::lock_guard<std::mutex> lock(mCore->mMutex);
mConsumerName = mCore->mConsumerName;
……
} // Autolock scope
……
{ // Autolock scope
……
eglDisplay = mSlots[found].mEglDisplay;
eglFence = mSlots[found].mEglFence;
// 不要在共享缓冲区模式下返回fence,除了第一帧。
*outFence = (mCore->mSharedBufferMode && mCore->mSharedBufferSlot == found) ?
Fence::NO_FENCE : mSlots[found].mFence;
mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
mSlots[found].mFence = Fence::NO_FENCE;
……
} // Autolock scope
……
if (eglFence != EGL_NO_SYNC_KHR) {
EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, 0, 1000000000);
……
eglDestroySyncKHR(eglDisplay, eglFence);
}
……
return returnFlags;
}
在 dequeue 这个进行的时候,会判断 OpenGL es 对应的 eglFence 是否有效,如果有效,则说明上一帧的 OpenGL es 中有部分工作还没有完成,就调用 eglClientWaitSyncKHR 进行 fence 的等待,直到 OpenGL es 中的唤醒,最后销毁。
2、queueBuffer
Surface::unlockAndPost
status_t Surface::unlockAndPost()
{
if (mLockedBuffer == nullptr) {
return INVALID_OPERATION;
}
int fd = -1;
status_t err = mLockedBuffer->unlockAsync(&fd);
err = queueBuffer(mLockedBuffer.get(), fd);
mPostedBuffer = mLockedBuffer;
mLockedBuffer = nullptr;
return err;
}
这里分为两个步骤:
- unlockAsync:解开 fence 的同步,接着会调用对应 gralloc 的 unlock 方法,解开当前进程对应 ion 内存的映射,释放虚拟内存。
- queueBuffer:调用 queueBuffer() 函数,进入 mQueueItem 中准备被消费。
queueBuffer
源码位置:/frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::queueBuffer(int slot,
const QueueBufferInput &input, QueueBufferOutput *output) {
……
int64_t requestedPresentTimestamp;
bool isAutoTimestamp;
android_dataspace dataSpace;
Rect crop(Rect::EMPTY_RECT);
int scalingMode;
uint32_t transform;
uint32_t stickyTransform;
sp<Fence> acquireFence;
bool getFrameTimestamps = false;
……
BufferItem item;
{ // Autolock scope
……
item.mFrameNumber = currentFrameNumber;
item.mSlot = slot;
item.mFence = acquireFence;
item.mFenceTime = acquireFenceTime;
item.mIsDroppable = mCore->mAsyncMode ||
(mConsumerIsSurfaceFlinger && mCore->mQueueBufferCanDrop) ||
(mCore->mLegacyBufferDrop && mCore->mQueueBufferCanDrop) ||
(mCore->mSharedBufferMode && mCore->mSharedBufferSlot == slot);
item.mSurfaceDamage = surfaceDamage;
item.mQueuedBuffer = true;
……
VALIDATE_CONSISTENCY();
} // Autolock scope
……
int connectedApi;
sp<Fence> lastQueuedFence;
{ // scope for the lock
……
lastQueuedFence = std::move(mLastQueueBufferFence);
mLastQueueBufferFence = std::move(acquireFence);
mLastQueuedCrop = item.mCrop;
mLastQueuedTransform = item.mTransform;
++mCurrentCallbackTicket;
mCallbackCondition.notify_all();
}
// 无锁等待
if (connectedApi == NATIVE_WINDOW_API_EGL) {
// 这里的等待允许两个满缓冲区排队,但不允许第三个。
lastQueuedFence->waitForever("Throttling EGL Production");
}
return NO_ERROR;
}
在这个过程,fence 的名字已经改成了 acquireFence。代表 Fence 已经更改成 acquire 状态。同时记录当前的 fence 为 mLastQueueBufferFence。
当下一个绘制的 GraphicBuffer 进来之后,如果是通过 OpenGL es 的 swapBuffer 触发的queueBuffer 操作,则会进行 lastQueuedFence 的 waitForever 进行阻塞。OpenGL es 在 eglswapBuffer() 函数中会进行 queueBuffer 以及 Surface 每一次通过 unlockAndPost 之后会把 Canvas 中的数据通过 queueBuffer 进行 GraphicBuffer 的队列压入。
通过 OpenGL es 入队的 api 都是 NATIVE_WINDOW_API_EGL,而通过 Canvas 的绘制好后 unlockAndPost 的操作对应的 api 是 NATIVE_WINDOW_API_CPU。
这样就会造成一个问题,两个不同的对象在不断的开始生产各自图元进行入队。当我们已经把一个通过 Skia 绘制好的 GraphicBuffer 传入了 hwc_set 并初始化好 fence 后,OpenGL es 绘制的图元到来会检测上一帧还没绘制就会等到上一帧绘制好后才能继续。
因此,这里意味着 OpenGL es 生产的图元和 CPU 生产的图元一起运到后续的步骤进行合成。为了处理 OpenGL es 绘制和 CPU 绘制每一帧花费的时间不同,一般的 OpenGL es 都是借助 GPU 进行绘制,因此会快上很多。Android 希望每一帧都卡在 16.6ms 左右也就是 60fps 中,如果 GPU 太快,而 CPU 太慢就会出现因为 GPU太 快导致 SurfaceFlinger 刷新的频率过高,从而使得性能消耗过大。如下图:
这就是源码注解中,Google 经过权衡之后,偏向选择牺牲 SurfaceFlinger 的吞吐量从而降低刷新频率的原因。
3、updateTexImage
在 acquireBuffer 过程中实际上就是选定需要跳多少帧之后需要显示的 GraphicBuffer,实际上 Fence 真正起作用的是它的调用者 updateTexImage。
源码位置:/frameworks/native/services/surfaceflinger/BufferLayerConsumer.cpp
status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime,
bool* autoRefresh, bool* queuedBuffer, uint64_t maxFrameNumber) {
……
Mutex::Autolock lock(mMutex);
……
// 释放前一个缓冲区
err = updateAndReleaseLocked(item, &mPendingRelease);
if (err != NO_ERROR) {
return err;
}
return err;
}
这里调用了 updateAndReleaseLocked() 函数释放前一帧的 GraphicBuffer。
updateAndReleaseLocked
此时已经从多个线程通过 Handler 转化到主线程中执行的步骤,因此到这一步需要释放上一帧的 GraphicBuffer 时候,需要把当前要释放的 GraphicBuffer 对应的 fence 阻塞起来,避免在 dequeueBuffer 的时候拿出来使用。主要对应其 GraphicBufferMapper 的 lockAsync 的方法。
status_t BufferLayerConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRelease* pendingRelease) {
status_t err = NO_ERROR;
int slot = item.mSlot;
……
std::shared_ptr<renderengine::ExternalTexture> nextTextureBuffer;
{
std::lock_guard<std::mutex> lock(mImagesMutex);
nextTextureBuffer = mImages[slot];
}
// 释放旧缓冲区
if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
if (pendingRelease == nullptr) {
status_t status = releaseBufferLocked(mCurrentTexture, mCurrentTextureBuffer->getBuffer());
if (status < NO_ERROR) {
err = status;
}
} else {
pendingRelease->currentTexture = mCurrentTexture;
pendingRelease->graphicBuffer = mCurrentTextureBuffer->getBuffer();
pendingRelease->isPending = true;
}
}
// 更新BufferLayerConsumer状态
……
return err;
}
这里面会判断 mCurrentTexture 是否已经有数据,如果有则添加到 pendingRelease 等到合成完毕后再释放,pendingRelease 为空直接释放上一帧的内容。
4、releaseBufferLocked
releaseBufferLocked 执行的时机有 2 处:
- updateAndReleaseLocked:如果没有 pendingReleaseFence,则直接释放上一帧的GraphicBuffer。
- 当 SurfaceFlinger 的合成完成后,进行 postComposition 收尾工作,会遍历 mLayersWithQueuedFrames(可视的 Layer)调用 releasePendingBuffer 方法。
bool BufferLayerConsumer::releasePendingBuffer() {
if (!mPendingRelease.isPending) {
return false;
}
Mutex::Autolock lock(mMutex);
status_t result = releaseBufferLocked(mPendingRelease.currentTexture, mPendingRelease.graphicBuffer);
……
mPendingRelease = PendingRelease();
return true;
}
该方法判断 mPendingRelease 的 isPending 是否为 true。一般都为 true,因为每一次绘制完都会调用 releasePendingBuffer 生成一个 PendingRelease 对象,告诉 updateAndRelease 需要延迟销毁。
这里延时销毁主要是因为在 SurfaceFlinger 中的 GraphicBuffer 有一种模式是共享模式,这种模式同样需要进行阻塞,不同的是,SurfaceFlinger 会不断的复用当前这个 GraphicBuffer 交给应用程序进行渲染。正因为这种特殊原因,需要每一次绘制完后进行才判断,而不是粗暴的直接在选取完需要绘制下一帧之前直接释放掉。接下来看看释放的核心方法。
releaseBuffer
源码位置:/frameworks/native/libs/gui/BufferQueueConsumer.cpp
status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
const sp<Fence>& releaseFence, EGLDisplay eglDisplay, EGLSyncKHR eglFence) {
……
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS || releaseFence == nullptr) {
return BAD_VALUE;
}
sp<IProducerListener> listener;
{ // Autolock scope
std::lock_guard<std::mutex> lock(mCore->mMutex);
if (frameNumber != mSlots[slot].mFrameNumber && !mSlots[slot].mBufferState.isShared()) {
return STALE_BUFFER_SLOT;
}
if (!mSlots[slot].mBufferState.isAcquired()) {
return BAD_VALUE;
}
mSlots[slot].mEglDisplay = eglDisplay;
mSlots[slot].mEglFence = eglFence;
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();
if (!mCore->mSharedBufferMode && mSlots[slot].mBufferState.isFree()) {
mSlots[slot].mBufferState.mShared = false;
}
if (!mSlots[slot].mBufferState.isShared()) {
mCore->mActiveBuffers.erase(slot);
mCore->mFreeBuffers.push_back(slot);
}
if (mCore->mBufferReleasedCbEnabled) {
listener = mCore->mConnectedProducerListener;
}
mCore->mDequeueCondition.notify_all();
VALIDATE_CONSISTENCY();
} // Autolock scope
// Call back without lock held
if (listener != nullptr) {
listener->onBufferReleased();
}
return NO_ERROR;
}
这个方法先把当前 fence 名字改成 releaseFence 说明此时 fence 已经进入会 release 状态。接着保存当前的 GraphicBuffer 等,判断到如果不是共享状态,则会把对应的 slot 从 mActiveBuffers 放到 mFreeBuffers 中,等到下一次 dequeue 时候使用,最后唤醒同一个线程的dequeue的阻塞。至此,整个 SurfaceFlinger 中 fence 的状态的流转已经解析完全。
总结
fence 本质上对应上内核中一个文件描述符 sync_file,sync_file 中有一个核心的 fence 结构体,用于预计释放的时间点。所有的 fence 都是以 sync_timeline 为基准进行递增的。
sync_timeline 作为内核记录已经渲染屏幕多少个时间点,每一次通过 hwc_set 都会递增一个时间点,并且会尝试的唤醒关联在 sync_timeline 中 active_list 中 sync_pt 的 fence 的阻塞。当 fence 合并之后,将会调用 fence_add_callback 重新把 fence_array 中的时间点都加到对应的 active_list 中。不过这一次唤醒,就需要 fence_array 中所有的 fence 都唤醒了,才会唤醒阻塞。
GraphicBuffer在 SurfaceFlinger 的状态流转:
接着再来看看 fence 在整个 SurfaceFlinger 中各个流程中担当了什么角色:
dequeueBuffer
GraphicBuffer 的从 SurfaceFlinger 出队到 app 进行绘制流程中。
- 调用 BufferQueueProducer 的 dequeue 方法,获取在 BufferQueueProducer 中空闲的 slot 对应的 GraphicBuffer,这个过程中,通过 fence 会等待 OpenGL es 在还没有完成的工作。
- 调用 GraphicBufferMapper 的 lockAsync 方法,对 ion 驱动中的内存地址进行一次内存映射,找到分配出来的图元绘制地址。这个过程如果是低版本或者不支持 lockAsync,则会进行等待上一帧 GraphicBuffer 的 fence 阻塞,直到对应该 GraphicBuffer 完成了 release 操作,才可能完成 Surface 的 lock 操作。
queueBuffer
GraphicBuffer 在应用绘制好后进入 SurfaceFlinger 进程。
- 首先 Surface 在 unlockAndPost 中先对 GraphicBuffer 解开内存映射,然后会进行阻塞,等到绘制的内容同步到 gralloc 中,才解开映射。
- 调用 BufferQueueProducer 的 queueBuffer 方法,把 fence 转化为 acquire 状态,GPU(OpenGL es)的完成这一帧的工作会等待CPU(Skia)的绘制工作,避免频率过快导致耗能过高。
图元消费合成
该方法会通过 acquireBuffer 决定跳多少帧,显示哪一帧的图元。之后会调用 updateAndReleaseLocked 方法。接着这里面会判断 mCurrentTexture 是否已经有数据,如果有则添加到 pendingRelease 等到合成完毕后再释放,pendingRelease 为空直接释放上一帧的内容。
释放图元因为有 Share 模式的图元将会把事释放事件从 updateAndReleaseLocked 推迟到 SurfaceFlinger 执行完合成步骤的 postComposition 开始释放。
releaseBufferLocked
这里把 GraphicBuffer 对应的索引放入 mFreeSlots 集合中等待是 dequeue 使用,同时把 fence 只是名字上转化为 release 状态。
一句话总结,Fence 的 acquire 状态其实是阻塞什么时候可以被消费,什么时候可以被渲染到屏幕。而 Fence 的 release 状态则是控制什么时候可以出队给应用进行绘制,什么时候可以被映射到内存。同时也会依据 OpenGL es 是否兼容 Android 的 fence 而是否提前设置本地纹理。