[052]Q平台上setBrightness的巨坑

2020-04-28  本文已影响0人  王小二的技术栈

前言

最近解决了一个掉帧的问题,从应用层来看是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显示之间建立起一个关系,一起配合调整,让用户对屏幕的观感效果更好。

上一篇下一篇

猜你喜欢

热点阅读