OH4.1 分离渲染挖洞透屏问题分析
分离渲染挖洞透屏问题分析
问题现象
使用xcomponent控件播放视频,richText控件
以备忘录应用为例,鼠标拖拽改变窗口大小
窗口变化过程中,在xcomponent控件周边观察到桌面
如何复现该缺陷
-
将system/etc/window/resources/window_manager_config.xml中的添加配置项102,将app默认启动模式改成floating
-
重启后,操作备忘录,进入其中一个笔记,鼠标拖拽改变备忘录大小
ps: 也可以用脚本一键应用
hdc shell mount -o remount,rw /
hdc file send window_manager_config.xml /etc/window/resources/
pause
hdc shell reboot
<?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
}
问题分析
上图是在复现将应用窗口拉大抓取的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次
由上可知,只有当UI侧 SetBounds 了,且UI侧任务完成flush到 RT,RT的帧才会去应用这个 SetBounds 后的 RosenWeb的大小
第四帧由于,UI侧还没flush它的SetBounds,所以此次Clip的大小是根据下面note变化随着一起拉伸变化的
那么还有疑问就是最后为什么跳过了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大小。
上图可见,note节点变化很快,RS会应用开始前的最后一次SetBounds,来改变note的大小。
那么问题来了,之前不是说会跟着改 rosenweb的大小吗,那是因为RT侧有了 RosenWeb的 SetBounds,所以会使用RosenWeb的SetBounds大小
note 节点开始改变
RosenWeb 节点改变
为什么会透屏?
我们通过上面的分析可知,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的变化的话是移动
就是因为,note在creatlayer时,它的dst 中的 w,h与 SrcRect [w,h] rawbuffer [w,h]不匹配,所以造成了图层的拉伸
上图是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。
可以看见,UI侧没有SetBounds,RS creatlayer时只存在 note的modify,RosenWeb是会和note有这等同差值的坐标变化的。
在拉伸窗口大小时(拉大或拉小H),通过日志可以看到,Rosen的layer的Y是会相应的产生变化的,说明了挖洞位置的区域是随着note的变化发生相应变化的。而Rosen由于之前分析的原因,它通过RT传到RS的位置信息是还没有变化的,所以图层拼接的时候会出现透屏现象。
拉大向下透屏
拉小向上透屏
更多推荐
所有评论(0)