一、问题背景

项目 说明
系统版本 OpenHarmony 6.1 Release
硬件平台 rk3568
主屏 MIPI,720x1280,竖屏(rotation=3 / ROTATION_270)
副屏 LVDS,1024x600,横屏(rotation=0)
渲染架构 divided render(isUniRender: 0),走 RSPhysicalScreenProcessor + HARDWARE_COMPOSITE 路径

在打开异显(EXPAND)后,出现两个关联问题:

  1. 副屏黑屏:副屏(LVDS 1024x600)完全黑屏,但 mirror 模式下副屏显示正常。
  2. 窗口拖拽内容消失:修复黑屏后,副屏上的窗口往右侧拖动时,右侧出现类似「遮罩」的区域,拖过去的窗口内容不显示。

二、问题现象

  • 黑屏:EXPAND 模式下副屏无任何内容输出;mirror 模式正常。
  • 遮罩:副屏窗口初始位置显示正常,但向右拖动窗口时,右侧一部分区域不再显示窗口内容,仿佛被一层遮罩挡住。

三、排查过程

排查过程经过了从 launcher → render_service → DMS → vsync → framebuffer 的逐步深入,最终定位到 render_service 的 divided render 绘制参数计算上的两个坐标系 bug。

关键排查手段是在 render_service、window_manager 等多处插入 ORIENT_DEBUG 日志,逐层确认每一环的数据是否正确:

  • dmserver 侧副屏 DisplayInfosize=[1024x600]displayOrientation=LANDSCAPEisDefaultVertical=0 —— 正确;
  • 窗口侧 viewport config:size=[600x500]orientation=LANDSCAPEtransformHint=0 —— 正确;
  • render_service 侧 surface:surfaceTransform=0buf=[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)。

CalculateSurfaceNodeClipRectselse 分支(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.matrixparams.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 架构下,副屏(非主屏)的窗口绘制在裁剪区域上存在坐标系混乱

  1. 坐标系不统一:clip 区域使用了全局坐标,而绘制矩阵和目标矩形使用本地坐标,导致裁剪区域错位、内容被整体裁掉(黑屏)。
  2. 操作顺序错误:clip 在窗口变换矩阵之前执行,导致裁剪区域无法跟随窗口移动,造成拖拽时内容消失(遮罩)。

修复的关键在于:保证 clip 区域与绘制矩阵使用统一的本地坐标系,并确保矩阵先于裁剪应用

这两个问题在 uni render(默认主屏渲染路径)下不会暴露,因为主屏窗口的 offset 通常为 0,且坐标基准天然一致;只有切换到 divided render 的多屏异显场景,副屏带非零 offset 时才会触发。这也是为什么 mirror 模式正常、EXPAND 模式异常的原因。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐