解决OH6.1 副屏expand模式下黑屏问题
一、问题背景
| 项目 | 说明 |
|---|---|
| 系统版本 | OpenHarmony 6.1 Release |
| 硬件平台 | rk3568 |
| 主屏 | MIPI,720x1280,竖屏(rotation=3 / ROTATION_270) |
| 副屏 | LVDS,1024x600,横屏(rotation=0) |
| 渲染架构 | divided render(isUniRender: 0),走 RSPhysicalScreenProcessor + HARDWARE_COMPOSITE 路径 |
在打开异显(EXPAND)后,出现两个关联问题:
- 副屏黑屏:副屏(LVDS 1024x600)完全黑屏,但 mirror 模式下副屏显示正常。
- 窗口拖拽内容消失:修复黑屏后,副屏上的窗口往右侧拖动时,右侧出现类似「遮罩」的区域,拖过去的窗口内容不显示。
二、问题现象
- 黑屏:EXPAND 模式下副屏无任何内容输出;mirror 模式正常。
- 遮罩:副屏窗口初始位置显示正常,但向右拖动窗口时,右侧一部分区域不再显示窗口内容,仿佛被一层遮罩挡住。
三、排查过程
排查过程经过了从 launcher → render_service → DMS → vsync → framebuffer 的逐步深入,最终定位到 render_service 的 divided render 绘制参数计算上的两个坐标系 bug。
关键排查手段是在 render_service、window_manager 等多处插入 ORIENT_DEBUG 日志,逐层确认每一环的数据是否正确:
- dmserver 侧副屏
DisplayInfo:size=[1024x600]、displayOrientation=LANDSCAPE、isDefaultVertical=0—— 正确; - 窗口侧 viewport config:
size=[600x500]、orientation=LANDSCAPE、transformHint=0—— 正确; - render_service 侧 surface:
surfaceTransform=0、buf=[600x500]、dst=[600x500]—— 尺寸和旋转都正确。
至此确认问题不在数据链路的「方向/尺寸」,而在 divided render 绘制时的坐标系(clip 区域)。
四、根因分析
4.1 Bug 1:clipRect 使用全局坐标导致黑屏
文件:foundation/graphic/graphic_2d/rosen/modules/render_service/core/pipeline/render_thread/rs_divided_render_util.cpp
在 divided render 架构下,副屏作为独立物理屏幕,在屏幕组(screen group)内存在非 0 的 offset(副屏位于主屏右侧,x 偏移为 1280)。
CalculateSurfaceNodeClipRects 的 else 分支(inLocalCoordinate == false)原本使用 node.GetDstRect() 来计算裁剪区域:
} else {
// 原实现(错误)
params.clipRect = node.GetDstRect(); // 返回全局逻辑屏幕坐标
params.clipRRect = RRect(node.GetDstRect(), params.cornerRadius);
}
node.GetDstRect() 返回的是全局逻辑屏幕坐标。对副屏上的窗口而言,其全局坐标的 x 从 1280 开始。而绘制时 params.matrix / params.dstRect 使用的是本地坐标(localBounds 从 0 开始),两者坐标系不一致,导致 clip 区域整体偏移 1280,窗口内容被全部裁剪掉,表现为副屏黑屏。
4.2 Bug 2:clip 在矩阵之前执行导致窗口拖拽内容消失
文件:foundation/graphic/graphic_2d/rosen/modules/render_service/core/pipeline/render_thread/rs_base_render_util.cpp
修复 Bug 1 后,clip 区域统一改成了窗口本地坐标 [0, 0, w, h]。但 SetPropertiesForCanvas 原来的执行顺序是:
// 原实现(错误顺序)
void RSBaseRenderUtil::SetPropertiesForCanvas(...)
{
canvas.ClipRect(params.clipRect, ...); // 先裁剪
...
canvas.ConcatMatrix(params.matrix); // 后应用矩阵
}
即先裁剪、后应用矩阵。由于 clip 使用窗口本地坐标,而窗口的真实位置是通过 matrix(含 offset)平移得到的,先执行 clip 意味着 clip 区域被固定在了 framebuffer 的左上角原点,不会跟随窗口移动。
| 窗口位置(副屏本地坐标) | clip 区域 | 结果 |
|---|---|---|
| (0, 56) | [0, 0, 600, 500] 恰好覆盖 | 正常显示 |
| (293, 53) | 仍为 [0, 0, 600, 500] | 窗口右侧 600~893 区域被裁掉 |
这就是「窗口向右拖时,右侧像遮罩、内容消失」的直接原因。
五、解决方案
5.1 修复 Bug 1:clipRect 统一改用本地坐标
文件:rs_divided_render_util.cpp
将 else 分支改为使用本地坐标(localBounds 从 0 开始),与 params.matrix、params.dstRect 保持一致:
void RSDividedRenderUtil::CalculateSurfaceNodeClipRects(
const RSSurfaceRenderNode& node,
const RectF& absBounds,
const RectF& localBounds,
bool inLocalCoordinate,
BufferDrawParam& params)
{
const RSProperties& property = node.GetRenderProperties();
params.cornerRadius = property.GetCornerRadius();
params.isNeedClip = property.GetClipToFrame();
if (inLocalCoordinate) {
// in canvas's local coordinate system.
params.clipRect = Drawing::Rect(0, 0, localBounds.GetWidth(), localBounds.GetHeight());
params.clipRRect = RRect(localBounds, params.cornerRadius);
} else {
// dstRect/matrix 均使用本地坐标(localBounds 从 0 开始),但 node.GetDstRect()
// 返回全局逻辑屏幕坐标(副屏在屏幕组内 offset 非 0,如 x=1280),会导致
// clip 区域错位、内容被全部裁掉(expand 副屏黑屏)。统一改用本地坐标。
params.clipRect = Drawing::Rect(0, 0, localBounds.GetWidth(), localBounds.GetHeight());
params.clipRRect = RRect(localBounds, params.cornerRadius);
}
}
5.2 修复 Bug 2:先应用矩阵,再裁剪
文件:rs_base_render_util.cpp
调整 SetPropertiesForCanvas 中的执行顺序,先 ConcatMatrix(matrix),再执行 clip,使裁剪区域跟随窗口的实际位置:
void RSBaseRenderUtil::SetPropertiesForCanvas(RSPaintFilterCanvas& canvas, const BufferDrawParam& params)
{
// 必须先 ConcatMatrix(matrix) 再 clip:clipRect/clipRRect 使用的是窗口本地坐标
// (localBounds 从 0 开始),只有先应用窗口的变换矩阵,clip 才会落到窗口的实际位置。
// 否则在 divided render 下,副屏窗口的 matrix 含 offset(如 x=1280),而 clip 在
// matrix 之前执行时,clip 区域固定在屏幕组原点上,导致窗口移动后内容被错误裁剪
// (副屏窗口拖到右侧时部分内容消失、出现“遮罩”现象)。
canvas.ConcatMatrix(params.matrix);
if (params.isNeedClip) {
if (!params.cornerRadius.IsZero()) {
canvas.ClipRoundRect(
RSPropertiesPainter::RRect2DrawingRRect(params.clipRRect), Drawing::ClipOp::INTERSECT, true);
} else {
canvas.ClipRect(params.clipRect, Drawing::ClipOp::INTERSECT, false);
}
}
if (Drawing::Color::ColorQuadGetA(params.backgroundColor) != Drawing::Color::COLOR_TRANSPARENT) {
canvas.DrawColor(params.backgroundColor);
}
}
六、验证结果
- 副屏在 EXPAND 模式下正常显示,launcher 壁纸铺满整个 1024x600。
- 副屏窗口拖拽到任意位置均能完整显示,不再出现「遮罩」现象。
- mirror 模式保持正常,无回归。
七、总结
本次问题的本质是 divided render 架构下,副屏(非主屏)的窗口绘制在裁剪区域上存在坐标系混乱:
- 坐标系不统一:clip 区域使用了全局坐标,而绘制矩阵和目标矩形使用本地坐标,导致裁剪区域错位、内容被整体裁掉(黑屏)。
- 操作顺序错误:clip 在窗口变换矩阵之前执行,导致裁剪区域无法跟随窗口移动,造成拖拽时内容消失(遮罩)。
修复的关键在于:保证 clip 区域与绘制矩阵使用统一的本地坐标系,并确保矩阵先于裁剪应用。
这两个问题在 uni render(默认主屏渲染路径)下不会暴露,因为主屏窗口的 offset 通常为 0,且坐标基准天然一致;只有切换到 divided render 的多屏异显场景,副屏带非零 offset 时才会触发。这也是为什么 mirror 模式正常、EXPAND 模式异常的原因。
更多推荐
所有评论(0)