分离渲染挖洞透屏问题分析

问题现象

使用xcomponent控件播放视频,richText控件
以备忘录应用为例,鼠标拖拽改变窗口大小
窗口变化过程中,在xcomponent控件周边观察到桌面

img

img

如何复现该缺陷

  1. 将system/etc/window/resources/window_manager_config.xml中的添加配置项102,将app默认启动模式改成floating

  2. 重启后,操作备忘录,进入其中一个笔记,鼠标拖拽改变备忘录大小

ps: 也可以用脚本一键应用

text

hdc shell mount -o remount,rw /
hdc file send window_manager_config.xml /etc/window/resources/
pause
hdc shell reboot

text

<?xml version='1.0' encoding="utf-8"?>
<!--
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 -->
 <Configs>
    <!--decor enable is true means app main window show decoration-->
    <decor enable="true"> </decor>
    <maxAppWindowNumber>100</maxAppWindowNumber>
    <minimizeByOther  enable="true"></minimizeByOther>
    <modeChangeHotZones>50 50 50</modeChangeHotZones>
    <stretchable  enable="false"></stretchable>
    <exitSplitRatios>0.1 0.9</exitSplitRatios>
    <splitRatios>0.5 0.33 0.67</splitRatios>
    <defaultWindowMode>1</defaultWindowMode>
    <windowAnimation>
        <timing>
            <!--duration of animation when add/remove window, unit is ms-->
            <duration>350</duration>
            <!--timing curve of animation, config it as below:
            name=ease, easeIn, easeOut, easeInOut, default, linear,
            spring, interactiveSpring, cubic(float float float float)-->
            <curve name="easeOut"></curve>
        </timing>
        <!--scaleX and scaleY of animation start state-->
        <scale>0.7 0.7</scale>
        <!--rotation of animation start state, 4 numbers is axis and angle-->
        <rotation>0 0 1 0</rotation>
        <!--translateX and translateY of animation start state-->
        <translate>0 0</translate>
        <!--opacity of animation start state-->
        <opacity>0</opacity>
    </windowAnimation>
    <!--keyboard animation config-->
    <keyboardAnimation>
        <timing>
            <!--duration of animation when add keyboard, unit is ms-->
            <durationIn>500</durationIn>
            <!--duration of animation when remove keyboard, unit is ms-->
            <durationOut>300</durationOut>
            <!--friction curve-->
            <curve name="cubic">0.2 0.0 0.2 1.0</curve>
        </timing>
    </keyboardAnimation>
    <!--startWindowTransitionAnimation enable is true means startWindow replace with alpha animation-->
    <remoteAnimation enable="true"></remoteAnimation>
    <windowEffect>
        <appWindows>
            <cornerRadius>
                <fullScreen>off</fullScreen>
                <split>off</split>
                <float>off</float>
            </cornerRadius>
            <shadow>
                <focused>
                    <elevation>0</elevation>
                    <color>#000000</color>
                    <offsetX>0</offsetX>
                    <offsetY>0</offsetY>
                    <alpha>0</alpha>
                </focused>
                <unfocused>
                    <elevation>0</elevation>
                    <color>#000000</color>
                    <offsetX>0</offsetX>
                    <offsetY>0</offsetY>
                    <alpha>0</alpha>
                </unfocused>
            </shadow>
        </appWindows>
    </windowEffect>
 </Configs>

RT处理挖孔代码

void RSRenderThreadVisitor::ProcessOtherSurfaceRenderNode(RSSurfaceRenderNode& node)
{
    auto clipRect = RSPaintFilterCanvas::GetLocalClipBounds(*canvas_);
#ifndef USE_ROSEN_DRAWING
    if (!clipRect.has_value() ||
        clipRect->width() < std::numeric_limits<float>::epsilon() ||
        clipRect->height() < std::numeric_limits<float>::epsilon()) {
#else
    if (!clipRect.has_value() ||
        clipRect->GetWidth() < std::numeric_limits<float>::epsilon() ||
        clipRect->GetHeight() < std::numeric_limits<float>::epsilon()) {
#endif
        // if clipRect is empty, this node will be removed from parent's children list.
        node.SetContextClipRegion(std::nullopt);

        static int pixel = 1;
        auto width = std::floor(node.GetRenderProperties().GetBoundsWidth() - (2 * pixel)); // width decrease 2 pixels
        auto height = std::floor(node.GetRenderProperties().GetBoundsHeight() - (2 * pixel)); // height decrease 2 pixels
        auto iter = surfaceCallbacks_.find(node.GetId());
        if (iter != surfaceCallbacks_.end()) {
#ifndef USE_ROSEN_DRAWING
            (iter->second)(canvas_->getTotalMatrix().getTranslateX(),
                canvas_->getTotalMatrix().getTranslateY(), width, height);
#else
            (iter->second)(canvas_->GetTotalMatrix().Get(Drawing::Matrix::TRANS_X),
                canvas_->GetTotalMatrix().Get(Drawing::Matrix::TRANS_Y), width, height);
#endif
        }
        return;
    }
    node.SetContextClipRegion(clipRect);
    // temporary workaround since ContextAlpha/ContextClipRegion happens after ApplyModifiers
    node.ApplyModifiers();

    RS_TRACE_BEGIN("ClipHoleForSurfaceNode");
    // clip hole
    ClipHoleForSurfaceNode(node);
    RS_TRACE_END();
}

void RSRenderThreadVisitor::ClipHoleForSurfaceNode(RSSurfaceRenderNode& node)
{
    // Calculation position in RenderService may appear floating point number, and it will be removed.
    // It caused missed line problem on surfaceview hap, so we subtract one pixel when cliphole to avoid this problem
    static int pixel = 1;
    auto x = std::ceil(node.GetRenderProperties().GetBoundsPositionX() + pixel); // x increase 1 pixel
    auto y = std::ceil(node.GetRenderProperties().GetBoundsPositionY() + pixel); // y increase 1 pixel
    auto width = std::floor(node.GetRenderProperties().GetBoundsWidth() - (2 * pixel)); // width decrease 2 pixels
    auto height = std::floor(node.GetRenderProperties().GetBoundsHeight() - (2 * pixel)); // height decrease 2 pixels
#ifndef USE_ROSEN_DRAWING
    canvas_->save();
    SkRect originRect = SkRect::MakeXYWH(x, y, width, height);
    canvas_->clipRect(originRect);

    auto iter = surfaceCallbacks_.find(node.GetId());
    if (iter != surfaceCallbacks_.end()) {
        (iter->second)(canvas_->getTotalMatrix().getTranslateX(), canvas_->getTotalMatrix().getTranslateY(), width, height);
    }

    if (node.IsNotifyRTBufferAvailable() && !node.GetIsForeground()) {
        ROSEN_LOGD("RSRenderThreadVisitor::ClipHoleForSurfaceNode node : %{public}" PRIu64 ","
            " clip [%{public}f, %{public}f, %{public}f, %{public}f]", node.GetId(), x, y, width, height);
        canvas_->clear(SK_ColorTRANSPARENT);
    } else {
        ROSEN_LOGD("RSRenderThreadVisitor::ClipHoleForSurfaceNode node : %{public}" PRIu64 ","
            " not clip [%{public}f, %{public}f, %{public}f, %{public}f]",
            node.GetId(), x, y, width, height);
        auto backgroundColor = node.GetRenderProperties().GetBackgroundColor();
        if (backgroundColor != RgbPalette::Transparent()) {
            canvas_->clear(backgroundColor.AsArgbInt());
        }
    }
    canvas_->restore();
#else
    canvas_->Save();
    Drawing::Rect originRect = Drawing::Rect(x, y, width + x, height + y);
    canvas_->ClipRect(originRect, Drawing::ClipOp::INTERSECT, false);
    auto iter = surfaceCallbacks_.find(node.GetId());
    if (iter != surfaceCallbacks_.end()) {
        (iter->second)(canvas_->GetTotalMatrix().Get(Drawing::Matrix::TRANS_X),
            canvas_->GetTotalMatrix().Get(Drawing::Matrix::TRANS_Y), width, height);
    }
    if (node.IsNotifyRTBufferAvailable() == true) {
        ROSEN_LOGI("cxw RSRenderThreadVisitor::ClipHoleForSurfaceNode node : %{public}" PRIu64 ","
            "clip [%{public}f, %{public}f, %{public}f, %{public}f]", node.GetId(), x, y, width, height);
        canvas_->Clear(Drawing::Color::COLOR_TRANSPARENT);
    } else {
        ROSEN_LOGI("cxw RSRenderThreadVisitor::ClipHoleForSurfaceNode node : %{public}" PRIu64 ","
            "not clip [%{public}f, %{public}f, %{public}f, %{public}f]", node.GetId(), x, y, width, height);
        auto backgroundColor = node.GetRenderProperties().GetBackgroundColor();
        if (backgroundColor != RgbPalette::Transparent()) {
            canvas_->Clear(backgroundColor.AsArgbInt());
        }
    }
    canvas_->Restore();
#endif // USE_ROSEN_DRAWING
}

问题分析

img

img

上图是在复现将应用窗口拉大抓取的trace和日志,可以看见RS一共有14帧,日志中也对应了14次 creatlayer,而在RT侧,我在开始和结束一帧RT的地方加入了 start和end,可以看到从RS开始更新后一共有12帧RT产生

可以通过trace和日志看到,每一帧的 RS 的 RosenWeb 节点都是在变化的,此节点的变化总共有两种,一是 note 节点发生变化,UI侧没有进行对 RosenWeb的 SetBounds,RT在apply的时候发现RSnode的属性有变化,在clip的时候会跟着note的变化对RosenWeb进行了相应坐标或者尺寸的改变。
二是UI侧调用了 RosenWeb 的SetBounds,RT直接根据这个SetBounds去修改了 RosenWeb 的大小和坐标。

我们可以从 RosenWeb 节点在 RS 侧的变化以及 UI侧的SetBounds可以进一步证明,分析。由于我们的动作是向下拉大窗口所以X , Y, W都没变化,只有H的变化,我们列出 RS H的变化
从RS可以对应上RT的第一帧开始也是是第3帧,到最后一共12帧
而UI侧一共SetBounds了13次

img

img

由上可知,只有当UI侧 SetBounds 了,且UI侧任务完成flush到 RT,RT的帧才会去应用这个 SetBounds 后的 RosenWeb的大小
第四帧由于,UI侧还没flush它的SetBounds,所以此次Clip的大小是根据下面note变化随着一起拉伸变化的

img

那么还有疑问就是最后为什么跳过了UI的最后第二第三的SetBounds,直接到最后一个了呢
是由于,在我们RT开始前,UI完整的SetBounds了三次,所以我们取的是最后一个 SetBounds

设置位置的代码

template<typename ModifierName, typename PropertyName, typename T>
void RSNode::SetProperty(RSModifierType modifierType, T value)
{
    std::unique_lock<std::recursive_mutex> lock(propertyMutex_);
    auto iter = propertyModifiers_.find(modifierType);
    if (iter != propertyModifiers_.end()) {
        auto property = std::static_pointer_cast<PropertyName>(iter->second->GetProperty());
        if (property == nullptr) {
            ROSEN_LOGE("RSNode::SetProperty: failed to set property, property is null!");
            return;
        }
        property->Set(value);
        return;
    }
    auto property = std::make_shared<PropertyName>(value);
    auto propertyModifier = std::make_shared<ModifierName>(property);
    propertyModifiers_.emplace(modifierType, propertyModifier);
    AddModifier(propertyModifier);
}


void RSSurfaceNode::OnBoundsSizeChanged() const
{
    auto bounds = GetStagingProperties().GetBounds();
    RS_TRACE_NAME_FMT("OnBoundsSizeChanged, Node: %s, Bounds: [%f %f %f %f]", GetName().c_str(), bounds.x_, bounds.y_, bounds.z_, bounds.w_); //add by cxw debug
    std::unique_ptr<RSCommand> command = std::make_unique<RSSurfaceNodeUpdateSurfaceDefaultSize>(
        GetId(), bounds.z_, bounds.w_);
    auto transactionProxy = RSTransactionProxy::GetInstance();
    if (transactionProxy != nullptr) {
        transactionProxy->AddCommand(command, true);
    }
#ifdef ROSEN_CROSS_PLATFORM
    if (!IsRenderServiceNode()) {
        std::unique_ptr<RSCommand> commandRt = std::make_unique<RSSurfaceNodeUpdateSurfaceDefaultSize>(
            GetId(), bounds.z_, bounds.w_);
        if (transactionProxy != nullptr) {
            transactionProxy->AddCommand(commandRt, false);
        }
    }
#endif
    std::lock_guard<std::mutex> lock(mutex_);
    if (boundsChangedCallback_) {
        boundsChangedCallback_(bounds);
    }
}

void RSNode::SetBounds(const Vector4f& bounds)
{
    SetProperty<RSBoundsModifier, RSAnimatableProperty<Vector4f>>(RSModifierType::BOUNDS, bounds);
    OnBoundsSizeChanged();
}

void RSNode::SetBounds(float positionX, float positionY, float width, float height)
{
    SetBounds({ positionX, positionY, width, height });
}

它会生成一个modify,RS以及RT都会去Modify到RSnode中 RS绘制的图层,note是直接用window进程调用SetBounds,然后RS在去执行applymodify来改变note大小。

img

上图可见,note节点变化很快,RS会应用开始前的最后一次SetBounds,来改变note的大小。
那么问题来了,之前不是说会跟着改 rosenweb的大小吗,那是因为RT侧有了 RosenWeb的 SetBounds,所以会使用RosenWeb的SetBounds大小

note 节点开始改变

img

RosenWeb 节点改变

img

为什么会透屏?

我们通过上面的分析可知,RS是会一帧帧执行RT传过来的内容,这个demo我们直接说成RosenWeb。而RS一帧执行的时候已经刷了很多帧RT了,我们送第一帧RT的内容时,RS的这帧会应用最新的note的midify,与我们送来的RosenWeb是不匹配的,note节点的缩放会在RS测将要挖孔的内容也进行等比例缩放
而我们传过来的RosenWeb还是很早前没缩放的大小,所以出现了透屏现象


//RS侧会去更新 RSNode 
//  ApplyModifiers();
 {
        mainLooping_.store(true);
        RenderFrameStart(timestamp_);
#if defined(RS_ENABLE_UNI_RENDER)
        WaitUntilSurfaceCapProcFinished();
#endif
        PerfMultiWindow();
        SetRSEventDetectorLoopStartTag();
        ROSEN_TRACE_BEGIN(HITRACE_TAG_GRAPHIC_AGP, "RSMainThread::DoComposition");
        ConsumeAndUpdateAllNodes();
        WaitUntilUnmarshallingTaskFinished();
        ProcessCommand();
        Animate(timestamp_);
        ApplyModifiers();
        CollectInfoForHardwareComposer();
        ProcessHgmFrameRate(timestamp_);
#if defined(RS_ENABLE_DRIVEN_RENDER)
        CollectInfoForDrivenRender();
#endif
        Render();
        InformHgmNodeInfo();
        ReleaseAllNodesBuffer();
        auto subThreadManager = RSSubThreadManager::Instance();
        subThreadManager->SubmitFilterSubThreadTask();
        SendCommands();
        {
            std::lock_guard<std::mutex> lock(context_->activeNodesInRootMutex_);
            context_->activeNodesInRoot_.clear();
        }
        ROSEN_TRACE_END(HITRACE_TAG_GRAPHIC_AGP);
        SetRSEventDetectorLoopFinishTag();
        rsEventManager_.UpdateParam();
        SKResourceManager::Instance().ReleaseResource();
#ifdef RS_ENABLE_PARALLEL_UPLOAD
        RSUploadResourceThread::Instance().OnRenderEnd();
#endif
        mainLooping_.store(false);
    };

void RSSurfaceRenderNode::OnApplyModifiers()
{
    auto& properties = GetMutableRenderProperties();
    auto geoPtr = (properties.GetBoundsGeometry());

    // Multiply context alpha into alpha
    properties.SetAlpha(properties.GetAlpha() * contextAlpha_);

    // Apply the context matrix into the bounds geometry
    geoPtr->SetContextMatrix(contextMatrix_);
    if (!ShouldPaint()) {
        UpdateFilterCacheStatusWithVisible(false);
    }
}

RT会传一个 note的 src 大小给到 RS,这个 note的大小是怎么来的呢 就是下图里面,note的creatlayer中 SrcRect [w,h] rawbuffer [w,h]

为什么只有w,h 因为这是代表了窗口大小的改变(宽高发生变化),只有x,y的变化的话是移动

img

就是因为,note在creatlayer时,它的dst 中的 w,h与 SrcRect [w,h] rawbuffer [w,h]不匹配,所以造成了图层的拉伸

img

上图是UI侧记录到的note位置,一一对应到相应的UI,然后UI侧在将这个位置信息传给对应的RT,RT在传buffer给对应的RS的时候,note的srcRect就是记录到的note位置信息

可以看到标记的note的H变化是 1059 1096 1109 1118 ,我们log里面打印对应RS中note的SrcRect中也是 059 1096 1109 1118。但是由于 RS中的dst是读取最新的note信息,所以造成了拉伸图层,导致透屏。

查看没有透屏时的log,也就是不改变大小时,UI侧不会去给RosenWeb SetBounds,RS侧会直接根据note的modify去相应修改RosenWeb的大小,不会有RT传过来的大小再改变RosenWeb。

img

可以看见,UI侧没有SetBounds,RS creatlayer时只存在 note的modify,RosenWeb是会和note有这等同差值的坐标变化的。

在拉伸窗口大小时(拉大或拉小H),通过日志可以看到,Rosen的layer的Y是会相应的产生变化的,说明了挖洞位置的区域是随着note的变化发生相应变化的。而Rosen由于之前分析的原因,它通过RT传到RS的位置信息是还没有变化的,所以图层拼接的时候会出现透屏现象。

img

拉大向下透屏

img

拉小向上透屏

img

Logo

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

更多推荐