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