护眼模式概述
研究表明,屏幕的蓝光会对睡眠质量造成不良影响。Android 7.1.1 推出一种称为“护眼模式”的功能,可减少设备屏幕发出的蓝光量,以便达到更接近用户当前时间和地点的自然光效果。Android 8.0 推出一项附加功能,可让用户更好地控制护眼模式效果的强度。 Android 10 推出了 COLOR_DISPLAY_SERVICE 系统服务,具备系统 API 接口,使系统、“设置”和系统界面能够更好地控制所有颜色转换(包括护眼模式)。
护眼模式需要实现 Hardware Composer HAL 2.0 (HWC 2),它可根据传递到 setColorTransform 的矩阵进行着色,而不会影响耗电量、性能和应用兼容性。
hardware/interfaces/graphics/composer/2.1/IComposerClient.hal
* SET_COLOR_TRANSFORM has this pseudo prototype
*
* setColorTransform(float[16] matrix,
* ColorTransform hint);
*
* Sets a color transform which will be applied after composition.
*
* If hint is not ColorTransform::ARBITRARY, then the device may use the
* hint to apply the desired color transform instead of using the color
* matrix directly.
*
* If the device is not capable of either using the hint or the matrix to
* apply the desired color transform, it must force all layers to client
* composition during VALIDATE_DISPLAY.
*
* If IComposer::Capability::SKIP_CLIENT_COLOR_TRANSFORM is present, then
* the client must never apply the color transform during client
* composition, even if all layers are being composed by the client.
*
* The matrix provided is an affine color transformation of the following
* form:
*
* |r.r r.g r.b 0|
* |g.r g.g g.b 0|
* |b.r b.g b.b 0|
* |Tr Tg Tb 1|
*
* This matrix must be provided in row-major form:
*
* {r.r, r.g, r.b, 0, g.r, ...}.
*
* Given a matrix of this form and an input color [R_in, G_in, B_in], the
* output color [R_out, G_out, B_out] will be:
*
* R_out = R_in * r.r + G_in * g.r + B_in * b.r + Tr
* G_out = R_in * r.g + G_in * g.g + B_in * b.g + Tg
* B_out = R_in * r.b + G_in * g.b + B_in * b.b + Tb
*
* @param matrix is a 4x4 transform matrix (16 floats) as described above.
* @param hint is a hint value which may be used instead of the given
* matrix unless it is ColorTransform::ARBITRARY.
*
实现
设备制造商可以使用在以下文件中定义的下列标记启用该功能的默认实现:frameworks/base/core/res/res/values/config.xml
<!-- Control whether Night display is available. This should only be enabled
on devices with HWC 2 color transform support. -->
<bool name="config_nightDisplayAvailable">false</bool>
<!-- Default mode to control how Night display is automatically activated.
One of the following values (see NightDisplayController.java):
0 - AUTO_MODE_DISABLED
1 - AUTO_MODE_CUSTOM
2 - AUTO_MODE_TWILIGHT
-->
界面功能
用户可以在“设置”>“显示”>“护眼模式”中设置护眼模式。在相应页面中,用户可以了解护眼模式,设置其时间安排以及开启或关闭护眼模式。
- 自动开启
永不:护眼模式一律不自动开启,且必须通过手动切换开启/关闭开关进行启用。
自定义时间安排:护眼模式在指定的开始时间(默认为晚上 10:30)开启,并在指定的结束时间(默认为早上 6:30)关闭。
日落到日出:在日落时开启护眼模式,在日出时关闭护眼模式。日出和日落的时间取决于设备所处的位置和当地的时节。 - 开启/关闭:用于控制护眼模式当前状态的切换开关。此状态遵循现有的自动规则。例如,如果用户在下午 5:30 开启护眼模式(早于自动规则的开启时间,即晚上 10:30),系统依然会在早上 6:30 关闭护眼模式。如果用户在早上 5:30 关闭护眼模式(早于自动规则的关闭时间,即早上 6:30),系统依然会在晚上 10:30 开启护眼模式。
- 强度:通过从暖色调滑动到冷色调以控制着色程度的拖动条。在未启用护眼模式时,可以停用拖动条。
- 信息性文本:向用户介绍护眼模式的功能和原理。
Setting Application 流程
- 色温调节
packages/apps/Settings/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
@Override
public boolean setSliderPosition(int position) {
return mColorDisplayManager.setNightDisplayColorTemperature(convertTemperature(position));
}
- 打开Night Light 模式
NightDisplayActivationPreferenceController.java
private final OnClickListener mListener = new OnClickListener() {
@Override
public void onClick(View v) {
mMetricsFeatureProvider.logClickedPreference(mPreference, getMetricsCategory());
mColorDisplayManager.setNightDisplayActivated(
!mColorDisplayManager.isNightDisplayActivated());
updateStateInternal(true);
}
Framework层 流程
一图顶千言,如下是设置色温的时序图。
1.setMatrix 函数,即设置色温矩阵。
@Override
public void setMatrix(int cct) {
if (mMatrix.length != 16) {
Slog.d(TAG, "The display transformation matrix must be 4x4");
return;
}
Matrix.setIdentityM(mMatrix, 0);
final float squareTemperature = cct * cct;
final float red = squareTemperature * mColorTempCoefficients[0]
+ cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
final float green = squareTemperature * mColorTempCoefficients[3]
+ cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
final float blue = squareTemperature * mColorTempCoefficients[6]
+ cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
mMatrix[0] = red;
mMatrix[5] = green;
mMatrix[10] = blue;
}
- setMatrix 的参数为最大色温和 最小色温之间的一个值。系统定义色温范围为 2596 -4082,色温是以开尔文为单位来计量的。.
<integer name="config_defaultNightDisplayAutoMode">0</integer>
<!-- Minimum color temperature, in Kelvin, supported by Night display. -->
<integer name="config_nightDisplayColorTemperatureMin">2596</integer>
<!-- Default color temperature, in Kelvin, to tint the screen when Night display is
activated. -->
<integer name="config_nightDisplayColorTemperatureDefault">2850</integer>
<!-- Maximum color temperature, in Kelvin, supported by Night display. -->
<integer name="config_nightDisplayColorTemperatureMax">4082</integer>
- 色温系数定义在如下文件中,设备制造商根据设备显示面板的特性(包括白点、色域和所需颜色)自定义颜色梯度。可以使用配置叠加更改颜色梯度,而不更改基本实现。此配置表示为红色、绿色和蓝色中每一种颜色的二次方程,其形式为 vres = vat2 + vbt + vy-int,其中 t 是以开尔文为单位的温度输入,根据 config_nightDisplayColorTemperatureMin 和 config_nightDisplayColorTemperatureMax 之间的范围(如上一部分所述)指定,va、vb 和 vy-int 分别是指定主曲线的 a 系数、b 系数和 y 轴截距,如下所示。
frameworks/base/core/res/res/values/config.xml
<string-array name="config_nightDisplayColorTemperatureCoefficientsNative">
<!-- R a-coefficient --> <item>0.0</item>
<!-- R b-coefficient --> <item>0.0</item>
<!-- R y-intercept --> <item>1.0</item>
<!-- G a-coefficient --> <item>-0.00000000962353339</item>
<!-- G b-coefficient --> <item>0.000153045476</item>
<!-- G y-intercept --> <item>0.390782778</item>
<!-- B a-coefficient --> <item>-0.0000000189359041</item>
<!-- B b-coefficient --> <item>0.000302412211</item>
<!-- B y-intercept --> <item>-0.198650895</item>
</string-array>
最后生成一下如下的矩阵:
computeColorMatrixLocked
上图中的矩阵只是护眼模式色温矩阵,实际上用户可能还设置了白平衡,颜色增加等效果如下,
/**
* Color transform level used by Night display to tint the display red.
*/
public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100;
/**
* Color transform level used by display white balance to adjust the display's white point.
*/
public static final int LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE = 125;
/**
* Color transform level used to adjust the color saturation of the display.
*/
public static final int LEVEL_COLOR_MATRIX_SATURATION = 150;
/**
* Color transform level used by A11y services to make the display monochromatic.
*/
public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200;
/**
* Color transform level used by A11y services to invert the display colors.
*/
public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
所以最终的ColorMatrix 是通过computeColorMatrixLocked 把所有效果的Matrix 相乘得到。如下
/**
* Returns the composition of all current color matrices, or {@code null} if there are none.
*/
@GuardedBy("mColorMatrix")
private float[] computeColorMatrixLocked() {
final int count = mColorMatrix.size();
if (count == 0) {
return null;
}
final float[][] result = mTempColorMatrix;
Matrix.setIdentityM(result[0], 0);
for (int i = 0; i < count; i++) {
float[] rhs = mColorMatrix.valueAt(i);
Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0);
}
return result[count % 2];
}
2.0 mCompositionEngine->present(refreshArgs)
最终的ColorMatrix 是通过surfaceflinger CompositionEngine->present()函数把矩阵设置SurfaceFlingre或者HWC层。
frameworks/native/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
void CompositionEngine::present(CompositionRefreshArgs& args) {
ATRACE_CALL();
ALOGV(__FUNCTION__);
preComposition(args);
{
// latchedLayers is used to track the set of front-end layer state that
// has been latched across all outputs for the prepare step, and is not
// needed for anything else.
LayerFESet latchedLayers;
for (const auto& output : args.outputs) {
output->prepare(args, latchedLayers);
}
}
updateLayerStateFromFE(args);
for (const auto& output : args.outputs) {
output->present(args);
}
}
- preComposition 函数,对于BufferLayer 主要是1.mFrameEventHistory记录PreComposition事件 2. 判断是否需要再触发SurfaceFlinger继续接受Vsync进行合成
- Output::prepare:Output实际上就是display, 通过调用每个Display的rebuildLayerStacks ,建立display 的 LayerStacks.
- Output->present:是真正执行合成的动作函数,接下来主要分析它。
frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp
void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
ATRACE_CALL();
ALOGV(__FUNCTION__);
updateColorProfile(refreshArgs);
updateAndWriteCompositionState(refreshArgs);
setColorTransform(refreshArgs);
beginFrame();
prepareFrame();
devOptRepaintFlash(refreshArgs);
finishFrame(refreshArgs);
postFramebuffer();
}
我们先关注 setColorTransform 函数
void Output::setColorTransform(const compositionengine::CompositionRefreshArgs& args) {
auto& colorTransformMatrix = editState().colorTransformMatrix;
if (!args.colorTransformMatrix || colorTransformMatrix == args.colorTransformMatrix) {
return;
}
colorTransformMatrix = *args.colorTransformMatrix;
dirtyEntireOutput();
}
把上层的颜色矩阵赋给 colorTransformMatrix ,之后通过dirtyEntireOutput 重置dirtyEntire 区域。我们知道图像合成分为Client和Device两中情况, 我们这里先按照Client合成流程往下走。
接着在finishFrame函数中调用composeSurfaces(Region::INVALID_REGION, refreshArgs); 如下:
void Output::finishFrame(const compositionengine::CompositionRefreshArgs& refreshArgs) {
ATRACE_CALL();
ALOGV(__FUNCTION__);
if (!getState().isEnabled) {
return;
}
// Repaint the framebuffer (if needed), getting the optional fence for when
// the composition completes.
auto optReadyFence = composeSurfaces(Region::INVALID_REGION, refreshArgs);
if (!optReadyFence) {
return;
}
// swap buffers (presentation)
mRenderSurface->queueBuffer(std::move(*optReadyFence));
}
composeSurfaces 函数很长,这里值截取colorTransform 相关code,如下:
std::optional<base::unique_fd> Output::composeSurfaces(
...
// Compute the global color transform matrix.
if (!outputState.usesDeviceComposition && !getSkipColorTransform()) {
clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
}
...
status_t status =
renderEngine.drawLayers(clientCompositionDisplay, clientCompositionLayerPointers,
buf->getNativeBuffer(), /*useFramebufferCache=*/true,
std::move(fd), &readyFence);
}
...
- 1:初始化clientCompositionDisplay对象, 对clientCompositionDisplay.colorTransform的赋值,只有在当前Frame 是Client 合成,且HWC 没有hal::Capability::SKIP_CLIENT_COLOR_TRANSFORM 能力的情况之下才会执行。(对于HWC的Capability 这个一块后面新起文档分析)
- 2:renderEngine.drawLayers 同过opengl 把所有层 进行颜色转换。
clientCompositionDisplay实际为renderengine::DisplaySettings 的对象。如下:
frameworks/native/libs/renderengine/include/renderengine/DisplaySettings.h
struct DisplaySettings {
// Rectangle describing the physical display. We will project from the
// logical clip onto this rectangle.
Rect physicalDisplay = Rect::INVALID_RECT;
// Rectangle bounded by the x,y- clipping planes in the logical display, so
// that the orthographic projection matrix can be computed. When
// constructing this matrix, z-coordinate bound are assumed to be at z=0 and
// z=1.
Rect clip = Rect::INVALID_RECT;
// Maximum luminance pulled from the display's HDR capabilities.
float maxLuminance = 1.0f;
// Output dataspace that will be populated if wide color gamut is used, or
// DataSpace::UNKNOWN otherwise.
ui::Dataspace outputDataspace = ui::Dataspace::UNKNOWN;
// Additional color transform to apply in linear space after transforming
// to the output dataspace.
mat4 colorTransform = mat4();
// Region that will be cleared to (0, 0, 0, 1) prior to rendering.
// This is specified in layer-stack space.
Region clearRegion = Region::INVALID_REGION;
// An additional orientation flag to be applied after clipping the output.
// By way of example, this may be used for supporting fullscreen screenshot
// capture of a device in landscape while the buffer is in portrait
// orientation.
uint32_t orientation = ui::Transform::ROT_0;
};
包括 Display的 颜色空间,颜色变换矩阵,旋转角度等。接着上面renderEngine::drawLayers函数分析,此函数是一个通用的DrawLayers函数,下面只保留颜色变换相关处理。
frameworks/native/libs/renderengine/gl/GLESRenderEngine.cpp
status_t GLESRenderEngine::drawLayers(const DisplaySettings& display,
const std::vector<const LayerSettings*>& layers,
ANativeWindowBuffer* const buffer,
const bool useFramebufferCache, base::unique_fd&& bufferFence,
base::unique_fd* drawFence) {
...
setColorTransform(display.colorTransform * layer->colorTransform);
...
else if (layer->geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
handleRoundedCorners(display, *layer, mesh);
} else {
drawMesh(mesh);
}
...
return NO_ERROR;
}
通过setColorTransform 函数把layer 和display的 颜色矩阵相乘,得到最终的colorMatrix 保存在mState.colorMatrix 中 。最终调用drawMesh绘制每一层Layer。
void GLESRenderEngine::drawMesh(const Mesh& mesh) {
...
Description managedState = mState;
// By default, DISPLAY_P3 is the only supported wide color output. However,
// when HDR content is present, hardware composer may be able to handle
// BT2020 data space, in that case, the output data space is set to be
// BT2020_HLG or BT2020_PQ respectively. In GPU fall back we need
// to respect this and convert non-HDR content to HDR format.
if (mUseColorManagement) {
Dataspace inputStandard = static_cast<Dataspace>(mDataSpace & Dataspace::STANDARD_MASK);
Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
Dataspace outputStandard =
static_cast<Dataspace>(mOutputDataSpace & Dataspace::STANDARD_MASK);
Dataspace outputTransfer =
static_cast<Dataspace>(mOutputDataSpace & Dataspace::TRANSFER_MASK);
bool needsXYZConversion = needsXYZTransformMatrix();
// NOTE: if the input standard of the input dataspace is not STANDARD_DCI_P3 or
// STANDARD_BT2020, it will be treated as STANDARD_BT709
if (inputStandard != Dataspace::STANDARD_DCI_P3 &&
inputStandard != Dataspace::STANDARD_BT2020) {
inputStandard = Dataspace::STANDARD_BT709;
}
if (needsXYZConversion) {
// The supported input color spaces are standard RGB, Display P3 and BT2020.
switch (inputStandard) {
case Dataspace::STANDARD_DCI_P3:
managedState.inputTransformMatrix = mDisplayP3ToXyz;
break;
case Dataspace::STANDARD_BT2020:
managedState.inputTransformMatrix = mBt2020ToXyz;
break;
default:
managedState.inputTransformMatrix = mSrgbToXyz;
break;
}
// The supported output color spaces are BT2020, Display P3 and standard RGB.
switch (outputStandard) {
case Dataspace::STANDARD_BT2020:
managedState.outputTransformMatrix = mXyzToBt2020;
break;
case Dataspace::STANDARD_DCI_P3:
managedState.outputTransformMatrix = mXyzToDisplayP3;
break;
default:
managedState.outputTransformMatrix = mXyzToSrgb;
break;
}
ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
managedState);
if (mState.drawShadows) {
glDrawElements(mesh.getPrimitive(), mesh.getIndexCount(), GL_UNSIGNED_SHORT,
mesh.getIndices());
} else {
glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
}
...
}
其中 ProgramCache::getInstance().useProgram 函数最终调用Program::setUniforms(const Description& desc)方法
void Program::setUniforms(const Description& desc) {
void Program::setUniforms(const Description& desc) {
...
if (mOutputTransformMatrixLoc >= 0) {
// The output transform matrix and color matrix can be combined as one matrix
// that is applied right before applying OETF.
mat4 outputTransformMatrix = desc.colorMatrix * desc.outputTransformMatrix;
glUniformMatrix4fv(mOutputTransformMatrixLoc, 1, GL_FALSE, outputTransformMatrix.asArray());
}
if (mDisplayMaxLuminanceLoc >= 0) {
glUniform1f(mDisplayMaxLuminanceLoc, desc.displayMaxLuminance);
}
if (mMaxMasteringLuminanceLoc >= 0) {
glUniform1f(mMaxMasteringLuminanceLoc, desc.maxMasteringLuminance);
}
if (mMaxContentLuminanceLoc >= 0) {
glUniform1f(mMaxContentLuminanceLoc, desc.maxContentLuminance);
}
if (mCornerRadiusLoc >= 0) {
glUniform1f(mCornerRadiusLoc, desc.cornerRadius);
}
if (mCropCenterLoc >= 0) {
glUniform2f(mCropCenterLoc, desc.cropSize.x / 2.0f, desc.cropSize.y / 2.0f);
}
// these uniforms are always present
glUniformMatrix4fv(mProjectionMatrixLoc, 1, GL_FALSE, desc.projectionMatrix.asArray());
}
关键函数 glUniformMatrix4fv(mOutputTransformMatrixLoc, 1, GL_FALSE, outputTransformMatrix.asArray());
通过一致变量 将outputTransformMatrix 颜色矩阵值传入GPU渲染管线,再调用glDrawElements或者glDrawArrays进行渲染。这样上层的颜色矩阵转化为opengl 指令,由GPU执行。敢兴趣的朋友可以查看如下详细着色脚本.
frameworks/native/libs/renderengine/gl/ProgramCache.cpp
总结
Client合成流程的颜色处理: 用户设置色温–>根据系统设置的色温系统求得相应ColorMatrix–>转化成OpenGL指令–>通过GPU对每层Layer的像素和ColorMatrix相乘–>得到最终颜色效果。Devices合成流程的颜色处理我们下回分析。