1 /*
2 * Copyright (c) 2024 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 "drawable/rs_property_drawable.h"
17
18 #include "rs_trace.h"
19
20 #include "common/rs_optional_trace.h"
21 #include "drawable/rs_property_drawable_utils.h"
22 #include "pipeline/rs_recording_canvas.h"
23 #include "pipeline/rs_render_node.h"
24 #include "pipeline/rs_surface_render_node.h"
25 #include "platform/common/rs_log.h"
26 #include "property/rs_filter_cache_manager.h"
27 #include "render/rs_drawing_filter.h"
28 #include "render/rs_linear_gradient_blur_shader_filter.h"
29
30 namespace OHOS::Rosen {
31 constexpr int AIBAR_CACHE_UPDATE_INTERVAL = 5;
32 constexpr int ROTATION_CACHE_UPDATE_INTERVAL = 1;
33 namespace DrawableV2 {
34 constexpr int TRACE_LEVEL_TWO = 2;
OnSync()35 void RSPropertyDrawable::OnSync()
36 {
37 if (!needSync_) {
38 return;
39 }
40 std::swap(drawCmdList_, stagingDrawCmdList_);
41 propertyDescription_ = stagingPropertyDescription_;
42 stagingPropertyDescription_.clear();
43 needSync_ = false;
44 }
45
OnPurge()46 void RSPropertyDrawable::OnPurge()
47 {
48 if (drawCmdList_) {
49 drawCmdList_->Purge();
50 }
51 }
52
CreateDrawFunc() const53 Drawing::RecordingCanvas::DrawFunc RSPropertyDrawable::CreateDrawFunc() const
54 {
55 auto ptr = std::static_pointer_cast<const RSPropertyDrawable>(shared_from_this());
56 return [ptr](Drawing::Canvas* canvas, const Drawing::Rect* rect) {
57 ptr->drawCmdList_->Playback(*canvas);
58 if (!ptr->propertyDescription_.empty()) {
59 RS_OPTIONAL_TRACE_NAME_FMT_LEVEL(TRACE_LEVEL_TWO, "RSPropertyDrawable:: %s, bounds:%s",
60 ptr->propertyDescription_.c_str(), rect->ToString().c_str());
61 }
62 };
63 }
64
65 // ============================================================================
66 // Updater
RSPropertyDrawCmdListUpdater(int width,int height,RSPropertyDrawable * target)67 RSPropertyDrawCmdListUpdater::RSPropertyDrawCmdListUpdater(int width, int height, RSPropertyDrawable* target)
68 : target_(target)
69 {
70 // PLANNING: use RSRenderNode to determine the correct recording canvas size
71 recordingCanvas_ = ExtendRecordingCanvas::Obtain(10, 10, false); // width 10, height 10
72 }
73
~RSPropertyDrawCmdListUpdater()74 RSPropertyDrawCmdListUpdater::~RSPropertyDrawCmdListUpdater()
75 {
76 if (recordingCanvas_ && target_) {
77 target_->stagingDrawCmdList_ = recordingCanvas_->GetDrawCmdList();
78 target_->needSync_ = true;
79 ExtendRecordingCanvas::Recycle(recordingCanvas_);
80 recordingCanvas_.reset();
81 target_ = nullptr;
82 } else {
83 ROSEN_LOGE("Update failed, recording canvas is null!");
84 }
85 }
86
GetRecordingCanvas() const87 const std::unique_ptr<ExtendRecordingCanvas>& RSPropertyDrawCmdListUpdater::GetRecordingCanvas() const
88 {
89 return recordingCanvas_;
90 }
91
92 // ============================================================================
OnGenerate(const RSRenderNode & node)93 RSDrawable::Ptr RSFrameOffsetDrawable::OnGenerate(const RSRenderNode& node)
94 {
95 if (auto ret = std::make_shared<RSFrameOffsetDrawable>(); ret->OnUpdate(node)) {
96 return std::move(ret);
97 }
98 return nullptr;
99 };
100
OnUpdate(const RSRenderNode & node)101 bool RSFrameOffsetDrawable::OnUpdate(const RSRenderNode& node)
102 {
103 const RSProperties& properties = node.GetRenderProperties();
104 auto frameOffsetX = properties.GetFrameOffsetX();
105 auto frameOffsetY = properties.GetFrameOffsetY();
106 if (frameOffsetX == 0 && frameOffsetY == 0) {
107 return false;
108 }
109
110 // regenerate stagingDrawCmdList_
111 RSPropertyDrawCmdListUpdater updater(0, 0, this);
112 updater.GetRecordingCanvas()->Translate(frameOffsetX, frameOffsetY);
113 return true;
114 }
115
116 // ============================================================================
OnGenerate(const RSRenderNode & node)117 RSDrawable::Ptr RSClipToBoundsDrawable::OnGenerate(const RSRenderNode& node)
118 {
119 auto ret = std::make_shared<RSClipToBoundsDrawable>();
120 ret->OnUpdate(node);
121 ret->OnSync();
122 return std::move(ret);
123 };
124
OnUpdate(const RSRenderNode & node)125 bool RSClipToBoundsDrawable::OnUpdate(const RSRenderNode& node)
126 {
127 const RSProperties& properties = node.GetRenderProperties();
128 RSPropertyDrawCmdListUpdater updater(0, 0, this);
129 auto& canvas = *updater.GetRecordingCanvas();
130 if (properties.GetClipBounds() != nullptr) {
131 canvas.ClipPath(properties.GetClipBounds()->GetDrawingPath(), Drawing::ClipOp::INTERSECT, true);
132 } else if (properties.GetClipToRRect()) {
133 canvas.ClipRoundRect(
134 RSPropertyDrawableUtils::RRect2DrawingRRect(properties.GetClipRRect()), Drawing::ClipOp::INTERSECT, true);
135 } else if (!properties.GetCornerRadius().IsZero()) {
136 canvas.ClipRoundRect(
137 RSPropertyDrawableUtils::RRect2DrawingRRect(properties.GetRRect()), Drawing::ClipOp::INTERSECT, true);
138 } else if (node.GetType() == RSRenderNodeType::SURFACE_NODE && RSSystemProperties::GetCacheEnabledForRotation() &&
139 node.ReinterpretCastTo<RSSurfaceRenderNode>()->IsAppWindow()) {
140 Drawing::Rect rect = RSPropertyDrawableUtils::Rect2DrawingRect(properties.GetBoundsRect());
141 Drawing::RectI iRect(static_cast<int>(rect.GetLeft()), static_cast<int>(rect.GetTop()),
142 static_cast<int>(rect.GetRight()), static_cast<int>(rect.GetBottom()));
143 canvas.ClipIRect(iRect, Drawing::ClipOp::INTERSECT);
144 } else {
145 canvas.ClipRect(
146 RSPropertyDrawableUtils::Rect2DrawingRect(properties.GetBoundsRect()), Drawing::ClipOp::INTERSECT, false);
147 }
148 return true;
149 }
150
OnGenerate(const RSRenderNode & node)151 RSDrawable::Ptr RSClipToFrameDrawable::OnGenerate(const RSRenderNode& node)
152 {
153 if (auto ret = std::make_shared<RSClipToFrameDrawable>(); ret->OnUpdate(node)) {
154 return std::move(ret);
155 }
156 return nullptr;
157 }
158
OnUpdate(const RSRenderNode & node)159 bool RSClipToFrameDrawable::OnUpdate(const RSRenderNode& node)
160 {
161 const RSProperties& properties = node.GetRenderProperties();
162 if (!properties.GetClipToFrame()) {
163 return false;
164 }
165
166 RSPropertyDrawCmdListUpdater updater(0, 0, this);
167 updater.GetRecordingCanvas()->ClipRect(
168 RSPropertyDrawableUtils::Rect2DrawingRect(properties.GetFrameRect()), Drawing::ClipOp::INTERSECT, false);
169 return true;
170 }
171
RSFilterDrawable()172 RSFilterDrawable::RSFilterDrawable()
173 {
174 if (RSProperties::FilterCacheEnabled) {
175 cacheManager_ = std::make_unique<RSFilterCacheManager>();
176 }
177 }
178
OnSync()179 void RSFilterDrawable::OnSync()
180 {
181 if (needSync_) {
182 filter_ = std::move(stagingFilter_);
183 needSync_ = false;
184 }
185
186 renderFilterHashChanged_ = stagingFilterHashChanged_;
187 renderForceClearCacheForLastFrame_ = stagingForceClearCacheForLastFrame_;
188 renderIsEffectNode_ = stagingIsEffectNode_;
189 renderIsSkipFrame_ = stagingIsSkipFrame_;
190 renderNodeId_ = stagingNodeId_;
191 renderClearType_ = stagingClearType_;
192 renderIntersectWithDRM_ = stagingIntersectWithDRM_;
193 renderIsDarkColorMode_ = stagingIsDarkColorMode_;
194
195 ClearFilterCache();
196
197 stagingFilterHashChanged_ = false;
198 stagingFilterRegionChanged_ = false;
199 stagingFilterInteractWithDirty_ = false;
200 stagingRotationChanged_ = false;
201 stagingForceClearCache_ = false;
202 stagingForceUseCache_ = false;
203 stagingIsOccluded_ = false;
204 stagingForceClearCacheForLastFrame_ = false;
205 stagingIntersectWithDRM_ = false;
206 stagingIsDarkColorMode_ = false;
207
208 stagingClearType_ = FilterCacheType::BOTH;
209 stagingIsLargeArea_ = false;
210 isFilterCacheValid_ = false;
211 stagingIsEffectNode_ = false;
212 stagingIsSkipFrame_ = false;
213 needSync_ = false;
214 }
215
CreateDrawFunc() const216 Drawing::RecordingCanvas::DrawFunc RSFilterDrawable::CreateDrawFunc() const
217 {
218 auto ptr = std::static_pointer_cast<const RSFilterDrawable>(shared_from_this());
219 return [ptr](Drawing::Canvas* canvas, const Drawing::Rect* rect) {
220 if (ptr->needDrawBehindWindow_) {
221 RS_TRACE_NAME_FMT("RSFilterDrawable::CreateDrawFunc DrawBehindWindow node[%llu] ", ptr->renderNodeId_);
222 auto paintFilterCanvas = static_cast<RSPaintFilterCanvas*>(canvas);
223 Drawing::AutoCanvasRestore acr(*canvas, true);
224 paintFilterCanvas->ClipRect(*rect);
225 Drawing::Rect absRect(0.0, 0.0, 0.0, 0.0);
226 canvas->GetTotalMatrix().MapRect(absRect, *rect);
227 Drawing::RectI bounds(std::ceil(absRect.GetLeft()), std::ceil(absRect.GetTop()),
228 std::ceil(absRect.GetRight()), std::ceil(absRect.GetBottom()));
229 RSPropertyDrawableUtils::DrawBackgroundEffect(paintFilterCanvas, ptr->filter_, ptr->cacheManager_,
230 ptr->renderClearFilteredCacheAfterDrawing_, bounds, true);
231 return;
232 }
233 if (canvas && ptr && ptr->renderIntersectWithDRM_) {
234 RS_TRACE_NAME_FMT("RSFilterDrawable::CreateDrawFunc IntersectWithDRM node[%lld] isDarkColorMode[%d]",
235 ptr->renderNodeId_, ptr->renderIsDarkColorMode_);
236 RSPropertyDrawableUtils::DrawFilterWithDRM(canvas, ptr->renderIsDarkColorMode_);
237 return;
238 }
239 if (canvas && ptr && ptr->filter_) {
240 RS_TRACE_NAME_FMT("RSFilterDrawable::CreateDrawFunc node[%llu] ", ptr->renderNodeId_);
241 if (ptr->filter_->GetFilterType() == RSFilter::LINEAR_GRADIENT_BLUR && rect != nullptr) {
242 auto filter = std::static_pointer_cast<RSDrawingFilter>(ptr->filter_);
243 std::shared_ptr<RSShaderFilter> rsShaderFilter =
244 filter->GetShaderFilterWithType(RSShaderFilter::LINEAR_GRADIENT_BLUR);
245 if (rsShaderFilter != nullptr) {
246 auto tmpFilter = std::static_pointer_cast<RSLinearGradientBlurShaderFilter>(rsShaderFilter);
247 tmpFilter->SetGeometry(*canvas, rect->GetWidth(), rect->GetHeight());
248 }
249 }
250 RSPropertyDrawableUtils::DrawFilter(canvas, ptr->filter_,
251 ptr->cacheManager_, ptr->IsForeground(), ptr->renderClearFilteredCacheAfterDrawing_);
252 }
253 };
254 }
255
GetFilterCachedRegion() const256 const RectI RSFilterDrawable::GetFilterCachedRegion() const
257 {
258 return cacheManager_ == nullptr ? RectI() : cacheManager_->GetCachedImageRegion();
259 }
260
MarkFilterRegionChanged()261 void RSFilterDrawable::MarkFilterRegionChanged()
262 {
263 stagingFilterRegionChanged_ = true;
264 }
265
MarkFilterRegionInteractWithDirty()266 void RSFilterDrawable::MarkFilterRegionInteractWithDirty()
267 {
268 stagingFilterInteractWithDirty_ = true;
269 }
270
MarkFilterRegionIsLargeArea()271 void RSFilterDrawable::MarkFilterRegionIsLargeArea()
272 {
273 stagingIsLargeArea_ = true;
274 }
275
MarkFilterForceUseCache(bool forceUseCache)276 void RSFilterDrawable::MarkFilterForceUseCache(bool forceUseCache)
277 {
278 stagingForceUseCache_ = forceUseCache;
279 }
280
MarkFilterForceClearCache()281 void RSFilterDrawable::MarkFilterForceClearCache()
282 {
283 stagingForceClearCache_ = true;
284 }
285
MarkRotationChanged()286 void RSFilterDrawable::MarkRotationChanged()
287 {
288 stagingRotationChanged_ = true;
289 }
290
MarkNodeIsOccluded(bool isOccluded)291 void RSFilterDrawable::MarkNodeIsOccluded(bool isOccluded)
292 {
293 stagingIsOccluded_ = isOccluded;
294 }
295
MarkForceClearCacheWithLastFrame()296 void RSFilterDrawable::MarkForceClearCacheWithLastFrame()
297 {
298 stagingForceClearCacheForLastFrame_ = true;
299 }
300
MarkNeedClearFilterCache()301 void RSFilterDrawable::MarkNeedClearFilterCache()
302 {
303 if (cacheManager_ == nullptr) {
304 return;
305 }
306
307 RS_TRACE_NAME_FMT("RSFilterDrawable::MarkNeedClearFilterCache nodeId[%llu], forceUseCache_:%d,"
308 "forceClearCache_:%d, hashChanged:%d, regionChanged_:%d, belowDirty_:%d,"
309 "lastCacheType:%d, cacheUpdateInterval_:%d, canSkip:%d, isLargeArea:%d, filterType_:%d, pendingPurge_:%d,"
310 "forceClearCacheWithLastFrame:%d, rotationChanged:%d",
311 stagingNodeId_, stagingForceUseCache_, stagingForceClearCache_, stagingFilterHashChanged_,
312 stagingFilterRegionChanged_, stagingFilterInteractWithDirty_,
313 lastCacheType_, cacheUpdateInterval_, canSkipFrame_, stagingIsLargeArea_,
314 filterType_, pendingPurge_, stagingForceClearCacheForLastFrame_, stagingRotationChanged_);
315
316 // if do not request NextVsync, close skip
317 if (stagingForceClearCacheForLastFrame_) {
318 cacheUpdateInterval_ = 0;
319 }
320
321 stagingIsSkipFrame_ = stagingIsLargeArea_ && canSkipFrame_ && !stagingFilterRegionChanged_;
322
323 // no valid cache
324 if (lastCacheType_ == FilterCacheType::NONE) {
325 UpdateFlags(FilterCacheType::NONE, false);
326 return;
327 }
328 // No need to invalidate cache if background image is not null or freezed
329 if (stagingForceUseCache_) {
330 UpdateFlags(FilterCacheType::NONE, true);
331 return;
332 }
333
334 // clear both two type cache: 1. force clear 2. filter region changed 3.skip-frame finished
335 // 4. background changed and effectNode rotated will enable skip-frame, the last frame need to update.
336 if (stagingForceClearCache_ || (stagingFilterRegionChanged_ && !stagingRotationChanged_) || NeedPendingPurge() ||
337 ((stagingFilterInteractWithDirty_ || stagingRotationChanged_) && cacheUpdateInterval_ <= 0)) {
338 UpdateFlags(FilterCacheType::BOTH, false);
339 return;
340 }
341
342 // clear snapshot cache last frame and clear filtered cache current frame
343 if (lastCacheType_ == FilterCacheType::FILTERED_SNAPSHOT && stagingFilterHashChanged_) {
344 UpdateFlags(FilterCacheType::FILTERED_SNAPSHOT, false);
345 return;
346 }
347
348 // when blur filter changes, we need to clear filtered cache if it valid.
349 UpdateFlags(stagingFilterHashChanged_ ?
350 FilterCacheType::FILTERED_SNAPSHOT : FilterCacheType::NONE, true);
351 }
352
353 //should be called in rs main thread
MarkBlurIntersectWithDRM(bool intersectWithDRM,bool isDark)354 void RSFilterDrawable::MarkBlurIntersectWithDRM(bool intersectWithDRM, bool isDark)
355 {
356 stagingIntersectWithDRM_ = intersectWithDRM;
357 stagingIsDarkColorMode_ = isDark;
358 }
359
IsFilterCacheValid() const360 bool RSFilterDrawable::IsFilterCacheValid() const
361 {
362 return isFilterCacheValid_;
363 }
364
IsSkippingFrame() const365 bool RSFilterDrawable::IsSkippingFrame() const
366 {
367 return (stagingFilterInteractWithDirty_ || stagingRotationChanged_) && cacheUpdateInterval_ > 0;
368 }
369
IsForceClearFilterCache() const370 bool RSFilterDrawable::IsForceClearFilterCache() const
371 {
372 return stagingForceClearCache_;
373 }
374
IsForceUseFilterCache() const375 bool RSFilterDrawable::IsForceUseFilterCache() const
376 {
377 return stagingForceUseCache_;
378 }
379
NeedPendingPurge() const380 bool RSFilterDrawable::NeedPendingPurge() const
381 {
382 return !stagingFilterInteractWithDirty_ && pendingPurge_;
383 }
384
MarkEffectNode()385 void RSFilterDrawable::MarkEffectNode()
386 {
387 stagingIsEffectNode_ = true;
388 }
389
RecordFilterInfos(const std::shared_ptr<RSFilter> & rsFilter)390 void RSFilterDrawable::RecordFilterInfos(const std::shared_ptr<RSFilter>& rsFilter)
391 {
392 auto filter = std::static_pointer_cast<RSDrawingFilter>(rsFilter);
393 if (filter == nullptr) {
394 return;
395 }
396 stagingFilterHashChanged_ = stagingCachedFilterHash_ != filter->Hash();
397 if (stagingFilterHashChanged_) {
398 stagingCachedFilterHash_ = filter->Hash();
399 }
400 filterType_ = filter->GetFilterType();
401 canSkipFrame_ = filter->CanSkipFrame();
402 }
403
ClearFilterCache()404 void RSFilterDrawable::ClearFilterCache()
405 {
406 if (!RSProperties::FilterCacheEnabled || cacheManager_ == nullptr || filter_ == nullptr) {
407 ROSEN_LOGD("Clear filter cache failed or no need to clear cache, filterCacheEnabled:%{public}d,"
408 "cacheManager:%{public}d, filter:%{public}d", RSProperties::FilterCacheEnabled,
409 cacheManager_ != nullptr, filter_ == nullptr);
410 return;
411 }
412 // 1. clear memory when region changed and is not the first time occured.
413 bool needClearMemoryForGpu = stagingFilterRegionChanged_ && cacheManager_->GetCachedType() != FilterCacheType::NONE;
414 if (filterType_ == RSFilter::AIBAR && stagingIsOccluded_) {
415 cacheManager_->InvalidateFilterCache(FilterCacheType::BOTH);
416 } else {
417 cacheManager_->InvalidateFilterCache(renderClearType_);
418 }
419 // 2. clear memory when region changed without skip frame.
420 needClearMemoryForGpu = needClearMemoryForGpu && cacheManager_->GetCachedType() == FilterCacheType::NONE;
421 if (needClearMemoryForGpu) {
422 cacheManager_->SetFilterInvalid(true);
423 }
424
425 // whether to clear blur images. true: clear blur image, false: clear snapshot
426 bool isSaveSnapshot = renderFilterHashChanged_ || cacheManager_->GetCachedType() == FilterCacheType::NONE;
427 bool isAIbarWithLastFrame = filterType_ == RSFilter::AIBAR && renderForceClearCacheForLastFrame_; // last vsync
428
429 if ((filterType_ != RSFilter::AIBAR || isAIbarWithLastFrame) && isSaveSnapshot) {
430 renderClearFilteredCacheAfterDrawing_ = true; // hold snapshot
431 } else {
432 renderClearFilteredCacheAfterDrawing_ = false; // hold blur image
433 }
434 if (renderIsEffectNode_ || renderIsSkipFrame_) { renderClearFilteredCacheAfterDrawing_ = renderFilterHashChanged_; }
435 lastCacheType_ = stagingIsOccluded_ ? cacheManager_->GetCachedType() : (renderClearFilteredCacheAfterDrawing_ ?
436 FilterCacheType::SNAPSHOT : FilterCacheType::FILTERED_SNAPSHOT);
437 RS_TRACE_NAME_FMT("RSFilterDrawable::ClearFilterCache nodeId[%llu], clearType:%d,"
438 " isOccluded_:%d, lastCacheType:%d needClearMemoryForGpu:%d ClearFilteredCacheAfterDrawing:%d",
439 renderNodeId_, renderClearType_, stagingIsOccluded_, lastCacheType_, needClearMemoryForGpu,
440 renderClearFilteredCacheAfterDrawing_);
441 }
442
443 // called after OnSync()
IsFilterCacheValidForOcclusion()444 bool RSFilterDrawable::IsFilterCacheValidForOcclusion()
445 {
446 auto cacheType = cacheManager_->GetCachedType();
447 RS_OPTIONAL_TRACE_NAME_FMT("RSFilterDrawable::IsFilterCacheValidForOcclusion cacheType:%d renderClearType_:%d",
448 cacheType, renderClearType_);
449
450 return cacheType != FilterCacheType::NONE;
451 }
452
UpdateFlags(FilterCacheType type,bool cacheValid)453 void RSFilterDrawable::UpdateFlags(FilterCacheType type, bool cacheValid)
454 {
455 stagingClearType_ = type;
456 isFilterCacheValid_ = cacheValid;
457 if (!cacheValid) {
458 cacheUpdateInterval_ = stagingRotationChanged_ ? ROTATION_CACHE_UPDATE_INTERVAL :
459 (filterType_ == RSFilter::AIBAR ? AIBAR_CACHE_UPDATE_INTERVAL :
460 (stagingIsLargeArea_ && canSkipFrame_ ? RSSystemProperties::GetFilterCacheUpdateInterval() : 0));
461 pendingPurge_ = false;
462 return;
463 }
464 if (stagingIsAIBarInteractWithHWC_) {
465 if (cacheUpdateInterval_ > 0) {
466 cacheUpdateInterval_--;
467 pendingPurge_ = true;
468 }
469 } else {
470 if ((stagingFilterInteractWithDirty_ || stagingRotationChanged_) && cacheUpdateInterval_ > 0) {
471 cacheUpdateInterval_--;
472 pendingPurge_ = true;
473 }
474 }
475 stagingIsAIBarInteractWithHWC_ = false;
476 }
477
IsAIBarCacheValid()478 bool RSFilterDrawable::IsAIBarCacheValid()
479 {
480 if (filterType_ != RSFilter::AIBAR) {
481 return false;
482 }
483 stagingIsAIBarInteractWithHWC_ = true;
484 RS_OPTIONAL_TRACE_NAME_FMT("IsAIBarCacheValid cacheUpdateInterval_:%d forceClearCacheForLastFrame_:%d",
485 cacheUpdateInterval_, stagingForceClearCacheForLastFrame_);
486 if (cacheUpdateInterval_ == 0 || stagingForceClearCacheForLastFrame_) {
487 return false;
488 } else {
489 MarkFilterForceUseCache(true);
490 return true;
491 }
492 }
493 } // namespace DrawableV2
494 } // namespace OHOS::Rosen
495