[052]Q平台上setBrightness的巨坑
前言
最近解决了一个掉帧的问题,从应用层来看是buffer申请不到,最后发现是Q平台升级+高通的代码+我们自己驱动优化算法导致的,三者缺一不可,由于保密协议,我只能简单的原生代码和简单的图来描述这个问题,避免大家踩坑。
一、Q平台上setBrightness的升级
1.1 Android Q
@Override
public void setBrightness(int brightness, int brightnessMode) {
synchronized (this) {
// LOW_PERSISTENCE cannot be manually set
if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mId +
": brightness=0x" + Integer.toHexString(brightness));
return;
}
// Ideally, we'd like to set the brightness mode through the SF/HWC as well, but
// right now we just fall back to the old path through Lights brightessMode is
// anything but USER or the device shouldBeInLowPersistenceMode().
if (brightnessMode == BRIGHTNESS_MODE_USER && !shouldBeInLowPersistenceMode()
&& mSurfaceControlMaximumBrightness == 255) {
// TODO: the last check should be mSurfaceControlMaximumBrightness != 0; the
// reason we enforce 255 right now is to stay consistent with the old path. In
// the future, the framework should be refactored so that brightness is a float
// between 0.0f and 1.0f, and the actual number of supported brightness levels
// is determined in the device-specific implementation.
if (DEBUG) {
Slog.d(TAG, "Using new setBrightness path!");
}
SurfaceControl.setDisplayBrightness(mDisplayToken,
(float) brightness / mSurfaceControlMaximumBrightness);
} else {
int color = brightness & 0x000000ff;
color = 0xff000000 | (color << 16) | (color << 8) | color;
setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
}
}
}
1.2 Android P
@Override
public void setBrightness(int brightness, int brightnessMode) {
synchronized (this) {
// LOW_PERSISTENCE cannot be manually set
if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mId +
": brightness=0x" + Integer.toHexString(brightness));
return;
}
int color = brightness & 0x000000ff;
color = 0xff000000 | (color << 16) | (color << 8) | color;
setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
}
}
1.3 两者的区别
we'd like to set the brightness mode through the SF/HWC as well
我们也希望通过SF/HWC设置屏幕亮度
就是一旦以下条件满足就会走Q版本上新的设置亮度流程
if (brightnessMode == BRIGHTNESS_MODE_USER && !shouldBeInLowPersistenceMode()
&& mSurfaceControlMaximumBrightness == 255)
Android Q上有两种方式设置屏幕亮度,如下图表示,导致掉帧的就是方式1
方式1:system_server->SF->HWC HAL->设备节点->背光驱动(Android Q)
方式2:system_server->Light HAL->设备节点->背光驱动(Android Q, P)
二、为什么会导致掉帧?
我在前言中已经说了是由Q平台升级+高通的代码+我们自己驱动优化算法导致的,三者缺一不可。
2.1 Q平台升级
出问题的项目就是走了方式1的流程,所以满足条件。
2.2 高通的代码
高通的HWC HAL层代码中对setDisplayBrightness接口实现中加了一个display的锁。也就意味这HWC其他的接口会和setDisplayBrightness产生锁的竞争关系。
2.3 我们自己驱动优化算法
我们在驱动中对背光设置有一些优化,在特定的情况下,会导致写设备节点的时间耗时200ms左右。
2.4 还原现场
首先lightsensor触发了自动背光调节,然后走SF-HWC去设置了亮度,持有了display的锁。
由于驱动的优化算法,导致这把锁持有了200ms。
这个200ms时间段里,SF绘制每一个帧的代码中也有和HWC的调用,因为拿不到锁导致也
block了,从而锁住了一个buffer。
应用申请一个buffer,完成绘制,交给sf,sf来不及使用。
应用又申请一个buffer,完成绘制,交给sf,sf来不及使用。
最后应用的三个buffer,一个处于lock,两个处于未用的状态(手机中bufferqueue设置的是3个)
应用再次申请buffer的时候,没有可用的buffer了,导致了主线程的block,最后导致了掉帧的问题的出现。
三、另外一个诡异的事情。
虽然问题基本已经解决,但是我无法解释,还有一个诡异的事情。
同一个手机,在驱动代码完全一样,只不过刷了不同高通基线的代码
竟然一个走方式1,一个走方式2。
日志发现的原因:
方式1的时候mSurfaceControlMaximumBrightness为255
方式2的时候mSurfaceControlMaximumBrightness为0
3.1 maximumBrightness为什么是0
基本可以猜到下面的代码在初始化的时候有异常,导致了maximumBrightness为0.
为什么会导致maximumBrightness为0,简单的说一下就是高通的基线升级导致了getDisplayBrightnessSupport返回了true,我就不展开讲了。
private LightImpl(Context context, int id) {
mId = id;
mDisplayToken = SurfaceControl.getInternalDisplayToken();
final boolean brightnessSupport = SurfaceControl.getDisplayBrightnessSupport(
mDisplayToken);
if (DEBUG) {
Slog.d(TAG, "Display brightness support: " + brightnessSupport);
}
int maximumBrightness = 0;
if (brightnessSupport) {
PowerManager pm = context.getSystemService(PowerManager.class);
if (pm != null) {
maximumBrightness = pm.getMaximumScreenBrightnessSetting();
}
}
mSurfaceControlMaximumBrightness = maximumBrightness;
}
3.2 负负得正
这个就是典型的负负得正,基线没有升级导致了getDisplayBrightnessSupport为false,导致了mSurfaceControlMaximumBrightness为0,最后走方式2,掉帧问题也就消失。
总结
基本上整个问题的分析过程,我是通过trace分析出来的,然后结合特定关键点的log,把这个问题给解决了。这是一个很有意思的问题,不方便放trace的截图,无法和大家分享如何看trace。
但是除了学会看trace的技巧,你一定要清楚的知道每一个进程,每一个线程之前的通信的关系,然后在自己的脑海中去还原现场,抽丝剥茧,找到问题点。
整个问题牵涉到APP-Framework-Kernel,如果你想要在性能优化上更近一步,我个人认为打通APP-Framework-Kernel是非常重要的一步。
尾巴
为什么Android Q上要大费周章通过SF/HWC去设置屏幕亮度,我推测是谷歌希望将屏幕亮度调节和屏幕UI显示之间建立起一个关系,一起配合调整,让用户对屏幕的观感效果更好。