安卓帧率FPS计算原理

本文最后更新于:2022年6月3日 晚上

FPS(帧率),即frames per second。

目前,帧率统计软件使用的信息来源主要有两个: 一个是基于dumpsys SurfaceFlinger --latency layer-name;另一个是基于dumpsys gfxinfo

本文不说怎么使用上面两个方法来统计帧率,主要说dumpsys SurfaceFlinger --latency layer-name的数据来源。

想知道怎么统计帧率的,可以参考下面两篇文章:

android 端监控方案分享 · TesterHome

介绍下基于 systrace gfx 统计帧率方案的实现过程 · TesterHome

执行dumpsys SurfaceFlinger --latency layer-name命令后,产生的输出来源于FrameTracker::dumpStats(std::string& result)方法。

frameworks/native/services/surfaceflinger/FrameTracker.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void FrameTracker::dumpStats(std::string& result) const {
Mutex::Autolock lock(mMutex);
processFencesLocked();

const size_t o = mOffset;
for (size_t i = 1; i < NUM_FRAME_RECORDS; i++) {
const size_t index = (o+i) % NUM_FRAME_RECORDS;
base::StringAppendF(&result, "%" PRId64 "\t%" PRId64 "\t%" PRId64 "\n",
mFrameRecords[index].desiredPresentTime,
mFrameRecords[index].actualPresentTime,
mFrameRecords[index].frameReadyTime);
}
result.append("\n");
}

先说结论:desiredPresentTime,actualPresentTime,frameReadyTime分别代表:

  • desiredPresentTime:queueBuffer 的时间戳
  • actualPresentTime:present fence signal 的时间戳
  • frameReadyTime:acquire fence signal 的时间戳

Fence的类型

Synchronization Framework

The Hardware Composer handles three types of sync fences:

  • Acquire fences are passed along with input buffers to the setLayerBuffer and setClientTarget calls. These represent a pending write into the buffer and must signal before the SurfaceFlinger or the HWC attempts to read from the associated buffer to perform composition.
  • Release fences are retrieved after the call to presentDisplay using the getReleaseFences call. These represent a pending read from the previous buffer on the same layer. A release fence signals when the HWC is no longer using the previous buffer because the current buffer has replaced the previous buffer on the display. Release fences are passed back to the app along with the previous buffers that will be replaced during the current composition. The app must wait until a release fence signals before writing new contents into the buffer that was returned to them.
  • Present fences are returned, one per frame, as part of the call to presentDisplay. Present fences represent when the composition of this frame has completed, or alternately, when the composition result of the prior frame is no longer needed. For physical displays, presentDisplay returns present fences when the current frame appears on the screen. After present fences are returned, it’s safe to write to the SurfaceFlinger target buffer again, if applicable. For virtual displays, present fences are returned when it’s safe to read from the output buffer.

在 Android 里面,总共有三类 fence —— acquire fence,release fence 和 present fence。其中,acquire fence 和 release fence 隶属于 Layer,present fence 隶属于帧(即 Layers):

acquire fence :App 将 Buffer 通过 queueBuffer() 还给 BufferQueue 的时候,此时该 Buffer 的 GPU 侧其实是还没有完成的,此时会带上一个 fence,这个 fence 就是 acquire fence。当 SurfaceFlinger/ HWC 要读取 Buffer 以进行合成操作的时候,需要等 acquire fence 释放之后才行。

release fence :当 App 通过 dequeueBuffer() 从 BufferQueue 申请 Buffer,要对 Buffer 进行绘制的时候,需要保证 HWC 已经不再需要这个 Buffer 了,即需要等 release fence signal 才能对 Buffer 进行写操作。

present fence :present fence 在 HWC1 的时候称为 retire fence,在 HWC2 中改名为 present fence。当前帧成功显示到屏幕的时候,present fence 就会 signal。

desiredPresentTime和frameReadyTime来自哪里(基于安卓12源码分析)

frameworks/native/services/surfaceflinger/FrameTracker.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// setDesiredPresentTime sets the time at which the current frame
// should be presented to the user under ideal (i.e. zero latency)
// conditions.
//理想条件下(即零延迟)呈现给用户的时间点。
void setDesiredPresentTime(nsecs_t desiredPresentTime);

// setFrameReadyTime sets the time at which the current frame became ready
// to be presented to the user. For example, if the frame contents is
// being written to memory by some asynchronous hardware, this would be
// the time at which those writes completed.
void setFrameReadyTime(nsecs_t readyTime);

// setFrameReadyFence sets the fence that is used to get the time at which
// the current frame became ready to be presented to the user.
void setFrameReadyFence(std::shared_ptr<FenceTime>&& readyFence);

// setActualPresentTime sets the timestamp at which the current frame became
// visible to the user.
void setActualPresentTime(nsecs_t displayTime);

// setActualPresentFence sets the fence that is used to get the time
// at which the current frame became visible to the user.
void setActualPresentFence(const std::shared_ptr<FenceTime>& fence);

frameworks/native/services/surfaceflinger/BufferLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
bool BufferLayer::onPostComposition(const DisplayDevice* display,
const std::shared_ptr<FenceTime>& glDoneFence,
const std::shared_ptr<FenceTime>& presentFence,
const CompositorTiming& compositorTiming) {
......

// Update mFrameTracker.
nsecs_t desiredPresentTime = mBufferInfo.mDesiredPresentTime;
//关键点1:设置desiredPresentTime
mFrameTracker.setDesiredPresentTime(desiredPresentTime);

......
std::shared_ptr<FenceTime> frameReadyFence = mBufferInfo.mFenceTime;
if (frameReadyFence->isValid()) {
//关键点2:设置frameReadyFence
mFrameTracker.setFrameReadyFence(std::move(frameReadyFence));
} else {
//纯软件绘制才走这里,frameReadyTime和desiredPresentTime就一样了
// There was no fence for this frame, so assume that it was ready
// to be presented at the desired present time.
mFrameTracker.setFrameReadyTime(desiredPresentTime);
}

if (display) {
const Fps refreshRate = display->refreshRateConfigs().getCurrentRefreshRate().getFps();
const std::optional<Fps> renderRate =
mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
if (presentFence->isValid()) {
//关键点3,设置actualPresentFence
mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence,
refreshRate, renderRate,
frameRateToSetFrameRateVotePayload(
mDrawingState.frameRate),
getGameMode());
mFlinger->mFrameTracer->traceFence(layerId, getCurrentBufferId(), mCurrentFrameNumber,
presentFence,
FrameTracer::FrameEvent::PRESENT_FENCE);
mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
} else if (const auto displayId = PhysicalDisplayId::tryCast(display->getId());
displayId && mFlinger->getHwComposer().isConnected(*displayId)) {
// The HWC doesn't support present fences, so use the refresh
// timestamp instead.
const nsecs_t actualPresentTime = display->getRefreshTimestamp();
mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime,
refreshRate, renderRate,
frameRateToSetFrameRateVotePayload(
mDrawingState.frameRate),
getGameMode());
mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(),
mCurrentFrameNumber, actualPresentTime,
FrameTracer::FrameEvent::PRESENT_FENCE);
//不支持fence,才设置成屏幕刷新周期时间戳
mFrameTracker.setActualPresentTime(actualPresentTime);
}
}
mFrameTracker.advanceFrame();
mBufferInfo.mFrameLatencyNeeded = false;
return true;
}

从上面代码来看,desiredPresentTime和frameReadyFence与mBufferInfo有关,接下来看一下它是在哪里赋值的。

frameworks/native/services/surfaceflinger/BufferQueueLayer.cpp

1
2
3
4
5
6
7
8
9
10
void BufferQueueLayer::gatherBufferInfo() {
BufferLayer::gatherBufferInfo();

//与desiredPresentTime有关
mBufferInfo.mDesiredPresentTime = mConsumer->getTimestamp();
//与frameReadyFence有关
mBufferInfo.mFenceTime = mConsumer->getCurrentFenceTime();
mBufferInfo.mFence = mConsumer->getCurrentFence();
......
}

继续往下追踪,看mConsumer的mCurrentTimestampmCurrentFenceTime是在哪里赋值的。

frameworks/native/services/surfaceflinger/BufferLayerConsumer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//获取mCurrentTimestamp
nsecs_t BufferLayerConsumer::getTimestamp() {
BLC_LOGV("getTimestamp");
Mutex::Autolock lock(mMutex);
return mCurrentTimestamp;
}

//获取mCurrentFenceTime
std::shared_ptr<FenceTime> BufferLayerConsumer::getCurrentFenceTime() const {
Mutex::Autolock lock(mMutex);
return mCurrentFenceTime;
}

status_t BufferLayerConsumer::updateAndReleaseLocked(const BufferItem& item,
PendingRelease* pendingRelease) {
status_t err = NO_ERROR;

int slot = item.mSlot;

BLC_LOGV("updateAndRelease: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture,
(mCurrentTextureBuffer != nullptr && mCurrentTextureBuffer->getBuffer() != nullptr)
? mCurrentTextureBuffer->getBuffer()->handle
: 0,
slot, mSlots[slot].mGraphicBuffer->handle);

// Hang onto the pointer so that it isn't freed in the call to
// releaseBufferLocked() if we're in shared buffer mode and both buffers are
// the same.

std::shared_ptr<renderengine::ExternalTexture> nextTextureBuffer;
{
std::lock_guard<std::mutex> lock(mImagesMutex);
nextTextureBuffer = mImages[slot];
}

// release old buffer
if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
if (pendingRelease == nullptr) {
status_t status =
releaseBufferLocked(mCurrentTexture, mCurrentTextureBuffer->getBuffer());
if (status < NO_ERROR) {
BLC_LOGE("updateAndRelease: failed to release buffer: %s (%d)", strerror(-status),
status);
err = status;
// keep going, with error raised [?]
}
} else {
pendingRelease->currentTexture = mCurrentTexture;
pendingRelease->graphicBuffer = mCurrentTextureBuffer->getBuffer();
pendingRelease->isPending = true;
}
}

// Update the BufferLayerConsumer state.
mCurrentTexture = slot;
mCurrentTextureBuffer = nextTextureBuffer;
mCurrentCrop = item.mCrop;
mCurrentTransform = item.mTransform;
mCurrentScalingMode = item.mScalingMode;
//mCurrentTimestamp在这里赋值
mCurrentTimestamp = item.mTimestamp;
mCurrentDataSpace = static_cast<ui::Dataspace>(item.mDataSpace);
mCurrentHdrMetadata = item.mHdrMetadata;
mCurrentFence = item.mFence;
//mCurrentFenceTime在这里赋值
mCurrentFenceTime = item.mFenceTime;
mCurrentFrameNumber = item.mFrameNumber;
mCurrentTransformToDisplayInverse = item.mTransformToDisplayInverse;
mCurrentSurfaceDamage = item.mSurfaceDamage;
mCurrentApi = item.mApi;

computeCurrentTransformMatrixLocked();

return err;
}

status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime,
bool* autoRefresh, bool* queuedBuffer,
uint64_t maxFrameNumber) {
ATRACE_CALL();
BLC_LOGV("updateTexImage");
Mutex::Autolock lock(mMutex);

if (mAbandoned) {
BLC_LOGE("updateTexImage: BufferLayerConsumer is abandoned!");
return NO_INIT;
}

BufferItem item;

// Acquire the next buffer.
// In asynchronous mode the list is guaranteed to be one buffer
// deep, while in synchronous mode we use the oldest buffer.
status_t err = acquireBufferLocked(&item, expectedPresentTime, maxFrameNumber);
if (err != NO_ERROR) {
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
err = NO_ERROR;
} else if (err == BufferQueue::PRESENT_LATER) {
// return the error, without logging
} else {
BLC_LOGE("updateTexImage: acquire failed: %s (%d)", strerror(-err), err);
}
return err;
}

if (autoRefresh) {
*autoRefresh = item.mAutoRefresh;
}

if (queuedBuffer) {
*queuedBuffer = item.mQueuedBuffer;
}

// We call the rejecter here, in case the caller has a reason to
// not accept this buffer. This is used by SurfaceFlinger to
// reject buffers which have the wrong size
int slot = item.mSlot;
if (rejecter && rejecter->reject(mSlots[slot].mGraphicBuffer, item)) {
releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
return BUFFER_REJECTED;
}

// Release the previous buffer.
//这里调用
err = updateAndReleaseLocked(item, &mPendingRelease);
if (err != NO_ERROR) {
return err;
}
return err;
}

从上面代码来看,mCurrentTimestamp和mCurrentFenceTime是updateAndReleaseLocked里面赋值的,而其又被updateTexImage调用,这个函数在systrace可视化页面很常见了,接下来分析与他们相关的mTimestamp和mFenceTime。

frameworks/native/libs/gui/include/gui/BufferItem.h

1
2
3
4
5
6
7
8
9
 // mTimestamp is the current timestamp for this buffer slot. This gets
// to set by queueBuffer each time this slot is queued. This value
// is guaranteed to be monotonically increasing for each newly
// acquired buffer.
//从这里的注释已经可以知道它是queueBuffer时的时间戳
int64_t mTimestamp;

// The std::shared_ptr<FenceTime> wrapper around mFence.
std::shared_ptr<FenceTime> mFenceTime{FenceTime::NO_FENCE};

frameworks/native/libs/gui/BufferQueueProducer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

status_t BufferQueueProducer::queueBuffer(int slot,
const QueueBufferInput &input, QueueBufferOutput *output) {
ATRACE_CALL();
ATRACE_BUFFER_INDEX(slot);

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;
//关键点
input.deflate(&requestedPresentTimestamp, &isAutoTimestamp, &dataSpace,
&crop, &scalingMode, &transform, &acquireFence, &stickyTransform,
&getFrameTimestamps);
const Region& surfaceDamage = input.getSurfaceDamage();
const HdrMetadata& hdrMetadata = input.getHdrMetadata();

if (acquireFence == nullptr) {
BQ_LOGE("queueBuffer: fence is NULL");
return BAD_VALUE;
}
//关键点
auto acquireFenceTime = std::make_shared<FenceTime>(acquireFence);

......

sp<IConsumerListener> frameAvailableListener;
sp<IConsumerListener> frameReplacedListener;
int callbackTicket = 0;
uint64_t currentFrameNumber = 0;
BufferItem item;
{ // Autolock scope
......
//这里赋值
item.mTimestamp = requestedPresentTimestamp;
item.mIsAutoTimestamp = isAutoTimestamp;
item.mDataSpace = dataSpace;
item.mHdrMetadata = hdrMetadata;
item.mFrameNumber = currentFrameNumber;
item.mSlot = slot;
item.mFence = acquireFence;
//这里赋值
item.mFenceTime = acquireFenceTime;
......

ATRACE_INT(mCore->mConsumerName.string(),
static_cast<int32_t>(mCore->mQueue.size()));
#ifndef NO_BINDER
mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
#endif
// Take a ticket for the callback functions
callbackTicket = mNextCallbackTicket++;

VALIDATE_CONSISTENCY();
} // Autolock scope
......
return NO_ERROR;
}

frameworks/native/libs/gui/include/gui/IGraphicBufferProducer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// timestamp - a monotonically increasing value in nanoseconds
// isAutoTimestamp - if the timestamp was synthesized at queue time
// dataSpace - description of the contents, interpretation depends on format
// crop - a crop rectangle that's used as a hint to the consumer
// scalingMode - a set of flags from NATIVE_WINDOW_SCALING_* in <window.h>
// transform - a set of flags from NATIVE_WINDOW_TRANSFORM_* in <window.h>

// acquireFenceTime相关
// fence - a fence that the consumer must wait on before reading the buffer,
// set this to Fence::NO_FENCE if the buffer is ready immediately

// sticky - the sticky transform set in Surface (only used by the LEGACY
// camera mode).
// getFrameTimestamps - whether or not the latest frame timestamps
// should be retrieved from the consumer.
// slot - the slot index to queue. This is used only by queueBuffers().
// queueBuffer() ignores this value and uses the argument `slot`
// instead.
inline QueueBufferInput(int64_t _timestamp, bool _isAutoTimestamp,
android_dataspace _dataSpace, const Rect& _crop,
int _scalingMode, uint32_t _transform, const sp<Fence>& _fence,
uint32_t _sticky = 0, bool _getFrameTimestamps = false,
int _slot = -1)
: timestamp(_timestamp), isAutoTimestamp(_isAutoTimestamp),
dataSpace(_dataSpace), crop(_crop), scalingMode(_scalingMode),
transform(_transform), stickyTransform(_sticky),
fence(_fence), surfaceDamage(),
getFrameTimestamps(_getFrameTimestamps), slot(_slot) { }

inline void deflate(int64_t* outTimestamp, bool* outIsAutoTimestamp,
android_dataspace* outDataSpace,
Rect* outCrop, int* outScalingMode,
uint32_t* outTransform, sp<Fence>* outFence,
uint32_t* outStickyTransform = nullptr,
bool* outGetFrameTimestamps = nullptr,
int* outSlot = nullptr) const {
//mTimestamp相关
*outTimestamp = timestamp;
*outIsAutoTimestamp = bool(isAutoTimestamp);
*outDataSpace = dataSpace;
*outCrop = crop;
*outScalingMode = scalingMode;
*outTransform = transform;
//acquireFenceTime相关
*outFence = fence;
if (outStickyTransform != nullptr) {
*outStickyTransform = stickyTransform;
}
if (outGetFrameTimestamps) {
*outGetFrameTimestamps = getFrameTimestamps;
}
if (outSlot) {
*outSlot = slot;
}
}

frameworks/native/libs/gui/Surface.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void Surface::getQueueBufferInputLocked(android_native_buffer_t* buffer, int fenceFd,
nsecs_t timestamp, IGraphicBufferProducer::QueueBufferInput* out) {
bool isAutoTimestamp = false;
//desiredPresentTime的真正来源
if (timestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) {
timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
isAutoTimestamp = true;
ALOGV("Surface::queueBuffer making up timestamp: %.2f ms",
timestamp / 1000000.0);
}

// Make sure the crop rectangle is entirely inside the buffer.
Rect crop(Rect::EMPTY_RECT);
mCrop.intersect(Rect(buffer->width, buffer->height), &crop);
//frameReadyFence真正来源
sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE);
//实例化QueueBufferInput,并传递timestamp和fence
IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp,
static_cast<android_dataspace>(mDataSpace), crop, mScalingMode,
mTransform ^ mStickyTransform, fence, mStickyTransform,
mEnableFrameTimestamps);

// we should send HDR metadata as needed if this becomes a bottleneck
input.setHdrMetadata(mHdrMetadata);

......
}


int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
ATRACE_CALL();
ALOGV("Surface::queueBuffer");
......

IGraphicBufferProducer::QueueBufferOutput output;
IGraphicBufferProducer::QueueBufferInput input;
//调用
getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
sp<Fence> fence = input.fence;

nsecs_t now = systemTime();
//调用
status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
mLastQueueDuration = systemTime() - now;
if (err != OK) {
ALOGE("queueBuffer: error queuing buffer, %d", err);
}

onBufferQueuedLocked(i, fence, output);
return err;
}

getQueueBufferInputLocked最终被 Surface::queueBuffer调用。

至此,已经分析完 desiredPresentTime 和 frameReadyTime 的最终来源了,即:

  • desiredPresentTime:queueBuffer 的时间戳
  • frameReadyTime:acquire fence signal 的时间戳

actualPresentTime指代的是什么

我就不分析这个了,流程都是差不多的,详情参考:

或许是迄今为止第一篇讲解 fps 计算原理的文章吧 - 掘金

其作者提到,计算一个 App 的 fps 的原理就是:

统计在一秒内该 App 往屏幕刷了多少帧,而在 Android 的世界里,每一帧显示到屏幕的标志是:present fence signal 了,因此计算 App 的 fps 就可以转换为:一秒内 App 的 Layer 有多少个有效 present fence signal 了(这里有效 present fence 是指,在本次 VSYNC 中该 Layer 有更新的 present fence)

参考文章

安卓帧率计算方案和背后原理剖析

或许是迄今为止第一篇讲解 fps 计算原理的文章吧 - 掘金