• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "feature/occlusion_culling/rs_occlusion_node.h"
17 
18 #include <sstream>
19 #include "platform/common/rs_log.h"
20 #include "pipeline/rs_render_node.h"
21 #include "pipeline/rs_uni_render_judgement.h"
22 #include "rs_trace.h"
23 
24 namespace OHOS::Rosen {
ForwardOrderInsert(std::shared_ptr<OcclusionNode> newNode)25 void OcclusionNode::ForwardOrderInsert(std::shared_ptr<OcclusionNode> newNode)
26 {
27     if (!newNode) {
28         return;
29     }
30     if (auto oldParent = newNode->parentOcNode_.lock()) {
31         oldParent->RemoveChild(newNode);
32     }
33     newNode->parentOcNode_ = weak_from_this();
34     if (!lastChild_) {
35         firstChild_ = newNode;
36     } else {
37         lastChild_->rightSibling_ = newNode;
38         newNode->leftSibling_ = lastChild_;
39     }
40     lastChild_ = newNode;
41     newNode->isValidInCurrentFrame_ = true;
42 }
43 
RemoveChild(const std::shared_ptr<OcclusionNode> & child)44 bool OcclusionNode::RemoveChild(const std::shared_ptr<OcclusionNode>& child)
45 {
46     if (!child) {
47         return false;
48     }
49     auto parentShared = child->parentOcNode_.lock();
50     if (parentShared == nullptr || parentShared.get() != this) {
51         return false;
52     }
53     auto left = child->leftSibling_.lock();
54     if (left) {
55         left->rightSibling_ = child->rightSibling_;
56     }
57     auto right = child->rightSibling_.lock();
58     if (right) {
59         right->leftSibling_ = child->leftSibling_;
60     }
61     if (child == firstChild_) {
62         firstChild_ = right;
63     }
64     if (child == lastChild_) {
65         lastChild_ = left;
66     }
67     child->parentOcNode_.reset();
68     child->leftSibling_.reset();
69     child->rightSibling_.reset();
70     return true;
71 }
72 
RemoveSubTree(std::unordered_map<NodeId,std::shared_ptr<OcclusionNode>> & occlusionNodes)73 void OcclusionNode::RemoveSubTree(std::unordered_map<NodeId, std::shared_ptr<OcclusionNode>>& occlusionNodes)
74 {
75     std::shared_ptr<OcclusionNode> child = lastChild_;
76     auto parentShared = parentOcNode_.lock();
77     if (parentShared) {
78         parentShared->RemoveChild(shared_from_this());
79     }
80     while (child) {
81         auto childLeft = child->leftSibling_.lock();
82         child->RemoveSubTree(occlusionNodes);
83         child = childLeft;
84     }
85     occlusionNodes.erase(id_);
86 }
87 
CollectNodeProperties(const RSRenderNode & node)88 void OcclusionNode::CollectNodeProperties(const RSRenderNode& node)
89 {
90     const auto& renderProperties = node.GetRenderProperties();
91     auto parentShared = parentOcNode_.lock();
92 
93     isSubTreeIgnored_ = (parentShared == nullptr) || IsSubTreeShouldIgnored(node, renderProperties);
94     if (isSubTreeIgnored_) {
95         return;
96     }
97     // The node is considered opaque if its background color is fully opaque,
98     // and it does not use brightness or color blend modes.
99     isBgOpaque_ = static_cast<uint8_t>(renderProperties.GetBackgroundColor().GetAlpha()) == UINT8_MAX &&
100         !renderProperties.IsBgBrightnessValid() && !renderProperties.IsFgBrightnessValid() &&
101         renderProperties.IsColorBlendModeNone();
102     rootOcclusionNode_ = parentShared->rootOcclusionNode_;
103     localScale_ = renderProperties.GetScale();
104     localAlpha_ = renderProperties.GetAlpha();
105     isNeedClip_ = renderProperties.GetClipToBounds() || renderProperties.GetClipToFrame() ||
106         renderProperties.GetClipToRRect();
107     cornerRadius_ = renderProperties.GetCornerRadius();
108 
109     CalculateDrawRect(node, renderProperties);
110 }
111 
IsSubTreeShouldIgnored(const RSRenderNode & node,const RSProperties & renderProperties)112 bool OcclusionNode::IsSubTreeShouldIgnored(const RSRenderNode& node, const RSProperties& renderProperties)
113 {
114     if (node.GetNodeGroupType() != RSRenderNode::NodeGroupType::NONE ||
115         node.GetIsTextureExportNode() || node.GetSharedTransitionParam() != nullptr ||
116         const_cast<RSRenderNode&>(node).GetOpincCache().IsSuggestOpincNode()) {
117         return true;
118     }
119 
120     const auto& skew = renderProperties.GetSkew();
121     const auto& perspective = renderProperties.GetPersp();
122     const auto& degree = renderProperties.GetRotation();
123     const auto& degreeX = renderProperties.GetRotationX();
124     const auto& degreeY = renderProperties.GetRotationY();
125 
126     // Skip this subtree if transformations (rotation/projection/skew/clip) make bounds non-rectangular.
127     if (!ROSEN_EQ(skew[0], 0.f) || !ROSEN_EQ(skew[1], 0.f) ||
128         !ROSEN_EQ(perspective[0], 0.f) || !ROSEN_EQ(perspective[1], 0.f) ||
129         !ROSEN_EQ(degree, 0.f) || !ROSEN_EQ(degreeX, 0.f) || !ROSEN_EQ(degreeY, 0.f) ||
130         renderProperties.GetClipBounds()) {
131         return true;
132     }
133 
134     // Skip this subtree if the node has any properties that may cause it to be drawn outside of its bounds.
135     const auto& originBounds = renderProperties.GetBounds();
136     const auto drawRect = RectF(originBounds.x_, originBounds.y_, originBounds.z_, originBounds.w_);
137     const auto& drawRegion = renderProperties.GetDrawRegion();
138     const auto& outline = renderProperties.GetOutline();
139     const auto& pixelStretch = renderProperties.GetPixelStretch();
140     const auto& distortionK = renderProperties.GetDistortionK();
141     auto foregroundFilter = RSUniRenderJudgement::IsUniRender() ? renderProperties.GetForegroundFilterCache()
142         : renderProperties.GetForegroundFilter();
143     const bool hasOutBoundsProp = (drawRegion && !drawRegion->IsInsideOf(drawRect)) ||
144         renderProperties.IsShadowValid() || outline || pixelStretch.has_value() ||
145         foregroundFilter || (distortionK.has_value() && *distortionK > 0);
146 
147     return hasOutBoundsProp;
148 }
149 
CalculateDrawRect(const RSRenderNode & node,const RSProperties & renderProperties)150 void OcclusionNode::CalculateDrawRect(const RSRenderNode& node, const RSProperties& renderProperties)
151 {
152     const auto& originBounds = renderProperties.GetBounds();
153     const auto& translate = renderProperties.GetTranslate();
154     const auto& center = renderProperties.GetPivot();
155     auto clipRRect = renderProperties.GetClipRRect().rect_;
156     // If the node has invalid properties, ignore the subtree.
157     isSubTreeIgnored_ |= !localScale_.IsValid() || !IsValid(localAlpha_) || !cornerRadius_.IsValid() ||
158         !originBounds.IsValid() || !translate.IsValid() || !center.IsValid() || !clipRRect.IsValid();
159     if (isSubTreeIgnored_) {
160         return;
161     }
162 
163     // calculate new pos.
164     auto centOffset = center * Vector2f(originBounds.z_, originBounds.w_);
165     localPosition_ = Vector2f(originBounds.x_, originBounds.y_);
166     localPosition_ += centOffset;
167     localPosition_ -= centOffset * localScale_;
168     localPosition_ += translate;
169 
170     // calculate drawRect.
171     drawRect_ = RectF(localPosition_.x_, localPosition_.y_,
172         originBounds.z_ * localScale_.x_, originBounds.w_ * localScale_.y_);
173     if (isNeedClip_ && !clipRRect.IsEmpty()) {
174         clipRRect.Move(translate.x_, translate.y_);
175         drawRect_ = drawRect_.IntersectRect(clipRRect);
176     }
177 }
178 
SafeCast(float value)179 static int16_t inline SafeCast(float value)
180 {
181     return static_cast<int16_t>(std::clamp(value, static_cast<float>(INT16_MIN), static_cast<float>(INT16_MAX)));
182 }
183 
ComputeOuter(RectF drawRect)184 static RectI16 inline ComputeOuter(RectF drawRect)
185 {
186     if (drawRect.IsEmpty()) {
187         return RectI16();
188     }
189     auto left = SafeCast(std::floor(drawRect.GetLeft()));
190     auto right = SafeCast(std::ceil(drawRect.GetRight()));
191     auto top = SafeCast(std::floor(drawRect.GetTop()));
192     auto bottom = SafeCast(std::ceil(drawRect.GetBottom()));
193     return (left >= right || top >= bottom) ? RectI16() : RectI16(left, top, right - left, bottom - top);
194 }
195 
ComputeInner(RectF drawRect,Vector4f cornerRadius)196 static RectI16 inline ComputeInner(RectF drawRect, Vector4f cornerRadius)
197 {
198     if (drawRect.IsEmpty()) {
199         return RectI16();
200     }
201     auto left = SafeCast(std::ceil(drawRect.GetLeft()));
202     auto right = SafeCast(std::floor(drawRect.GetRight()));
203     auto top = SafeCast(std::ceil(drawRect.GetTop() + std::max(cornerRadius.x_, cornerRadius.y_)));
204     auto bottom = SafeCast(std::floor(drawRect.GetBottom() - std::max(cornerRadius.w_, cornerRadius.z_)));
205     return (left >= right || top >= bottom) ? RectI16() : RectI16(left, top, right - left, bottom - top);
206 }
207 
CalculateNodeAllBounds()208 void OcclusionNode::CalculateNodeAllBounds()
209 {
210     auto parentShared = parentOcNode_.lock();
211     if (parentShared == nullptr || isSubTreeIgnored_) {
212         return;
213     }
214 
215     accumulatedAlpha_ = parentShared->localAlpha_ * parentShared->accumulatedAlpha_;
216     auto alpha = localAlpha_ * accumulatedAlpha_;
217     isAlphaNeed_ = alpha < 1.f;
218     accumulatedScale_ = parentShared->localScale_ * parentShared->accumulatedScale_;
219     absPositions_ = parentShared->absPositions_ + localPosition_ * accumulatedScale_;
220     auto scaleDrawRect = RectF(drawRect_.GetLeft() * accumulatedScale_.x_,
221         drawRect_.GetTop() * accumulatedScale_.y_,
222         drawRect_.GetWidth() * accumulatedScale_.x_,
223         drawRect_.GetHeight() * accumulatedScale_.y_);
224     auto absDrawRect = scaleDrawRect.Offset(parentShared->absPositions_.x_, parentShared->absPositions_.y_);
225     outerRect_ = ComputeOuter(absDrawRect);
226     innerRect_ = ComputeInner(absDrawRect, cornerRadius_);
227     clipOuterRect_ = parentShared->clipOuterRect_;
228     clipInnerRect_ = parentShared->clipInnerRect_;
229     outerRect_ = outerRect_.IntersectRect(clipOuterRect_);
230     innerRect_ = innerRect_.IntersectRect(clipInnerRect_);
231 
232     if (isNeedClip_) {
233         clipOuterRect_ = outerRect_;
234         clipInnerRect_ = innerRect_;
235     }
236 
237     isOutOfRootRect_ = IsOutOfRootRect(outerRect_);
238 }
239 
IsOutOfRootRect(const RectI16 & absDrawRect)240 bool OcclusionNode::IsOutOfRootRect(const RectI16 &absDrawRect)
241 {
242     auto shared_root = rootOcclusionNode_.lock();
243     if (!shared_root) {
244         return false;
245     }
246     return absDrawRect.GetLeft() >= shared_root->clipOuterRect_.GetRight() ||
247         absDrawRect.GetRight() < 0 ||
248         absDrawRect.GetTop() >= shared_root->clipOuterRect_.GetBottom() ||
249         absDrawRect.GetBottom() < 0 ||
250         absDrawRect.IsEmpty();
251 }
252 
UpdateClipRect(const RSRenderNode & node)253 void OcclusionNode::UpdateClipRect(const RSRenderNode& node)
254 {
255     auto& renderProperties = node.GetRenderProperties();
256     int16_t boundsWidth = renderProperties.GetBoundsWidth();
257     int16_t boundsHeight = renderProperties.GetBoundsHeight();
258     clipOuterRect_.SetAll(0, 0, boundsWidth, boundsHeight);
259     clipInnerRect_.SetAll(0, 0, boundsWidth, boundsHeight);
260 }
261 
UpdateSubTreeProp()262 void OcclusionNode::UpdateSubTreeProp()
263 {
264     isValidInCurrentFrame_ = true;
265     if (isSubTreeIgnored_) {
266         return;
267     }
268     auto parentShared = parentOcNode_.lock();
269     if (parentShared) {
270         CalculateNodeAllBounds();
271     }
272     std::shared_ptr<OcclusionNode> child = lastChild_;
273     // Update the subtree properties of the current node and its children
274     // Each child node is only updated once per frame.
275     while (child && !child->isValidInCurrentFrame_) {
276         child->UpdateSubTreeProp();
277         child = child->leftSibling_.lock();
278     }
279 }
280 
DetectOcclusion(std::unordered_set<NodeId> & culledNodes,std::unordered_set<NodeId> & culledEntireSubtree,std::unordered_set<NodeId> & offTreeNodes)281 void OcclusionNode::DetectOcclusion(std::unordered_set<NodeId>& culledNodes,
282     std::unordered_set<NodeId>& culledEntireSubtree, std::unordered_set<NodeId>& offTreeNodes)
283 {
284     RS_TRACE_NAME_FMT("OcclusionNode::DetectOcclusion, root node id is : %lu", id_);
285     OcclusionCoverageInfo globalCoverage;
286     (void)DetectOcclusionInner(globalCoverage, culledNodes, culledEntireSubtree, offTreeNodes);
287 }
288 
DetectOcclusionInner(OcclusionCoverageInfo & globalCoverage,std::unordered_set<NodeId> & culledNodes,std::unordered_set<NodeId> & culledEntireSubtree,std::unordered_set<NodeId> & offTreeNodes)289 OcclusionCoverageInfo OcclusionNode::DetectOcclusionInner(OcclusionCoverageInfo& globalCoverage,
290     std::unordered_set<NodeId>& culledNodes, std::unordered_set<NodeId>& culledEntireSubtree,
291     std::unordered_set<NodeId>& offTreeNodes)
292 {
293     isValidInCurrentFrame_ = false;
294     occludedById_ = 0;
295     // Process nodes with high z-order first
296     std::shared_ptr<OcclusionNode> child = lastChild_;
297     OcclusionCoverageInfo maxChildCoverage;
298     OcclusionCoverageInfo selfCoverage;
299     // Before traversing child nodes, check if this node is already fully covered by the global occlusion region.
300     // This check can be optimized to cull the entire subtree in the future.
301     CheckNodeOcclusion(globalCoverage, culledNodes, culledEntireSubtree);
302     while (child) {
303         if (!child->isSubTreeIgnored_ && child->isValidInCurrentFrame_) {
304             const auto& childCoverInfo = child->DetectOcclusionInner(globalCoverage, culledNodes,
305                 culledEntireSubtree, offTreeNodes);
306             if (childCoverInfo.area_ > maxChildCoverage.area_) {
307                 maxChildCoverage = childCoverInfo;
308             }
309         } else if (!child->isValidInCurrentFrame_) {
310             offTreeNodes.insert(child->id_);
311         } else {
312             child->isValidInCurrentFrame_ = false;
313         }
314         child = child->leftSibling_.lock();
315     }
316     CheckNodeOcclusion(maxChildCoverage, culledNodes);
317     UpdateCoverageInfo(globalCoverage, selfCoverage);
318     // Return the maximum coverage info of the subtree
319     if (maxChildCoverage.area_ > selfCoverage.area_) {
320         return maxChildCoverage;
321     }
322     return selfCoverage;
323 }
324 
CheckNodeOcclusion(OcclusionCoverageInfo & coverageInfo,std::unordered_set<NodeId> & culledNodes)325 void OcclusionNode::CheckNodeOcclusion(OcclusionCoverageInfo& coverageInfo, std::unordered_set<NodeId>& culledNodes)
326 {
327     // Currently, only canvas nodes without clipping attributes can be culled
328     if (type_ == RSRenderNodeType::CANVAS_NODE && !isNeedClip_) {
329         if (isOutOfRootRect_ || outerRect_.IsInsideOf(coverageInfo.rect_)) {
330             culledNodes.insert(id_);
331             occludedById_ = coverageInfo.id_;
332         }
333     }
334 }
335 
CheckNodeOcclusion(OcclusionCoverageInfo & coverageInfo,std::unordered_set<NodeId> & culledNodes,std::unordered_set<NodeId> & culledEntireSubtree)336 void OcclusionNode::CheckNodeOcclusion(OcclusionCoverageInfo& coverageInfo, std::unordered_set<NodeId>& culledNodes,
337     std::unordered_set<NodeId>& culledEntireSubtree)
338 {
339     if (type_ != RSRenderNodeType::CANVAS_NODE) {
340         return;
341     }
342     if (isOutOfRootRect_ || outerRect_.IsInsideOf(coverageInfo.rect_)) {
343         if (!hasChildrenOutOfRect_) {
344             culledEntireSubtree.insert(id_);
345         } else if (!isNeedClip_) {
346             culledNodes.insert(id_);
347         }
348         occludedById_ = coverageInfo.id_;
349     }
350 }
351 
UpdateCoverageInfo(OcclusionCoverageInfo & globalCoverage,OcclusionCoverageInfo & selfCoverage)352 void OcclusionNode::UpdateCoverageInfo(OcclusionCoverageInfo& globalCoverage, OcclusionCoverageInfo& selfCoverage)
353 {
354     // When the node is a opaque node, update the global coverage info
355     if (IsOpaque()) {
356         selfCoverage.area_ = innerRect_.GetWidth() * innerRect_.GetHeight();
357         selfCoverage.rect_ = innerRect_;
358         selfCoverage.id_ = id_;
359         if (selfCoverage.area_ > globalCoverage.area_) {
360             globalCoverage = selfCoverage;
361         }
362     }
363 }
364 
365 // Used for debugging, comparing rs node tree and occlusion node tree
PreorderTraversal(std::vector<std::shared_ptr<OcclusionNode>> & result)366 void OcclusionNode::PreorderTraversal(std::vector<std::shared_ptr<OcclusionNode>>& result)
367 {
368     if (isSubTreeIgnored_) {
369         return;
370     }
371     RS_TRACE_NAME_FMT("%s node id %llu", __func__, id_);
372     result.push_back(shared_from_this());
373     std::shared_ptr<OcclusionNode> child = firstChild_;
374     while (child) {
375         child->PreorderTraversal(result);
376         child = child->rightSibling_.lock();
377     }
378 }
379 
GetOcclusionNodeInfoString()380 std::string OcclusionNode::GetOcclusionNodeInfoString()
381 {
382     std::stringstream ss;
383     ss << "OcclusionNode id: " << std::to_string(id_)
384        << ", occludedBy id: " << std::to_string(occludedById_)
385        << ", outerRectCoords left: " << outerRect_.GetLeft()
386        << ", right: " << outerRect_.GetRight()
387        << ", top: " << outerRect_.GetTop()
388        << ", bottom: " << outerRect_.GetBottom()
389        << ", innerRectCoords left: " << innerRect_.GetLeft()
390        << ", right: " << innerRect_.GetRight()
391        << ", top: " << innerRect_.GetTop()
392        << ", bottom: " << innerRect_.GetBottom()
393        << ", subTreeIgnore: " << isSubTreeIgnored_
394        << ", outOfScreen: " << isOutOfRootRect_
395        << ", bgOpaque: " << isBgOpaque_
396        << ", alpha_: " << localAlpha_ * accumulatedAlpha_
397        << ", isNeedClip_: " << isNeedClip_
398        << ", hasChildrenOutOfRect_: " << hasChildrenOutOfRect_;
399     return ss.str();
400 }
401 } // namespace OHOS::Rosen
402