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