1 /*
2 * Copyright (c) 2023 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 "property/rs_filter_cache_manager.h"
17
18 #ifndef USE_ROSEN_DRAWING
19 #include "common/rs_optional_trace.h"
20 #include "platform/common/rs_log.h"
21 #include "platform/common/rs_system_properties.h"
22 #include "render/rs_skia_filter.h"
23 #include "src/image/SkImage_Base.h"
24
25 #ifdef RS_ENABLE_GL
26 #include "include/gpu/GrBackendSurface.h"
27 #endif
28
29 namespace OHOS {
30 namespace Rosen {
IsLargeArea(int width,int height)31 inline bool IsLargeArea(int width, int height)
32 {
33 // Use configurable threshold to determine if the area is large, and apply different cache policy.
34 // [PLANNING]: dynamically adjust the cache policy (e.g. update interval and cache area expansion) according to the
35 // cache size / dirty region percentage / current frame rate / filter radius.
36 static auto threshold = RSSystemProperties::GetFilterCacheSizeThreshold();
37 return width > threshold && height > threshold;
38 }
39
UpdateCacheStateWithFilterHash(uint32_t filterHash)40 void RSFilterCacheManager::UpdateCacheStateWithFilterHash(uint32_t filterHash)
41 {
42 if (cacheType_ != CacheType::CACHE_TYPE_FILTERED_SNAPSHOT) {
43 return;
44 }
45 // If we are caching a filtered snapshot, we need to check if the filter hash matches.
46 if (filterHash == cachedFilterHash_) {
47 return;
48 }
49
50 RS_OPTIONAL_TRACE_FUNC_BEGIN();
51 ROSEN_LOGD(
52 "RSFilterCacheManager::UpdateCacheStateWithFilterHash Cache expired. Reason: Cached filtered snapshot %X "
53 "does not match filter hash %X.",
54 cachedFilterHash_, filterHash);
55 InvalidateCache();
56 RS_OPTIONAL_TRACE_FUNC_END();
57 }
58
UpdateCacheStateWithFilterRegion(const RectI & filterRegion)59 void RSFilterCacheManager::UpdateCacheStateWithFilterRegion(const RectI& filterRegion)
60 {
61 if (cacheType_ == CacheType::CACHE_TYPE_NONE) {
62 return;
63 }
64
65 // Test if the filter region is contained in the cached region.
66 auto skFilterRegion = SkIRect::MakeLTRB(
67 filterRegion.GetLeft(), filterRegion.GetTop(), filterRegion.GetRight(), filterRegion.GetBottom());
68 if (cachedImageRegion_.contains(skFilterRegion)) {
69 filterRegion_ = skFilterRegion;
70 return;
71 }
72 RS_OPTIONAL_TRACE_FUNC_BEGIN();
73 ROSEN_LOGD("RSFilterCacheManager::UpdateCacheStateWithFilterRegion Cache expired. Reason: Filter region is not "
74 "within the cached region.");
75 InvalidateCache();
76 RS_OPTIONAL_TRACE_FUNC_END();
77 }
78
UpdateCacheStateWithFilterRegion(bool isCachedRegionCannotCoverFilterRegion)79 void RSFilterCacheManager::UpdateCacheStateWithFilterRegion(bool isCachedRegionCannotCoverFilterRegion)
80 {
81 if (cacheType_ == CacheType::CACHE_TYPE_NONE || isCachedRegionCannotCoverFilterRegion == false) {
82 return;
83 }
84 RS_OPTIONAL_TRACE_FUNC_BEGIN();
85
86 ROSEN_LOGD("RSFilterCacheManager::UpdateCacheStateWithFilterRegion Cache expired. Reason: Filter region is not "
87 "within the cached region.");
88 InvalidateCache();
89 RS_OPTIONAL_TRACE_FUNC_END();
90 }
91
UpdateCacheStateWithDirtyRegion(const RectI & dirtyRegion)92 void RSFilterCacheManager::UpdateCacheStateWithDirtyRegion(const RectI& dirtyRegion)
93 {
94 if (cacheType_ == CacheType::CACHE_TYPE_NONE) {
95 return;
96 }
97
98 // Use the dirty region to determine if the cache is valid.
99 auto SkDirtyRegion =
100 SkIRect::MakeLTRB(dirtyRegion.GetLeft(), dirtyRegion.GetTop(), dirtyRegion.GetRight(), dirtyRegion.GetBottom());
101 // The underlying image is not affected by the dirty region, cache is valid.
102 if (!SkDirtyRegion.intersect(filterRegion_)) {
103 return;
104 }
105
106 RS_OPTIONAL_TRACE_FUNC_BEGIN();
107 // The underlying image is affected by the dirty region, determine if the cache should be invalidated by cache age.
108 // [PLANNING]: also take into account the filter radius / cache size / percentage of intersected area.
109 if (cacheUpdateInterval_ > 0) {
110 ROSEN_LOGD("RSFilterCacheManager::UpdateCacheStateWithDirtyRegion Delaying cache invalidation for %d frames.",
111 cacheUpdateInterval_);
112 } else {
113 InvalidateCache();
114 }
115 RS_OPTIONAL_TRACE_FUNC_END();
116 }
117
UpdateCacheStateWithDirtyRegion(bool isIntersectedWithDirtyRegion)118 void RSFilterCacheManager::UpdateCacheStateWithDirtyRegion(bool isIntersectedWithDirtyRegion)
119 {
120 if (cacheType_ == CacheType::CACHE_TYPE_NONE || isIntersectedWithDirtyRegion == false) {
121 return;
122 }
123 RS_OPTIONAL_TRACE_FUNC_BEGIN();
124
125 // The underlying image is affected by the dirty region, determine if the cache should be invalidated by cache age.
126 // [PLANNING]: also take into account the filter radius / cache size / percentage of intersected area.
127 if (cacheUpdateInterval_ > 0) {
128 ROSEN_LOGD("RSFilterCacheManager::UpdateCacheStateWithDirtyRegion Delaying cache invalidation for %d frames.",
129 cacheUpdateInterval_);
130 } else {
131 InvalidateCache();
132 }
133 RS_OPTIONAL_TRACE_FUNC_END();
134 }
135
DrawFilter(RSPaintFilterCanvas & canvas,const std::shared_ptr<RSSkiaFilter> & filter)136 void RSFilterCacheManager::DrawFilter(RSPaintFilterCanvas& canvas, const std::shared_ptr<RSSkiaFilter>& filter)
137 {
138 // Filter validation is not needed, since it's already done in RSPropertiesPainter::DrawFilter.
139 auto clipIBounds = canvas.getDeviceClipBounds();
140 if (clipIBounds.isEmpty()) {
141 // clipIBounds is empty, no need to draw filter.
142 return;
143 }
144 RS_OPTIONAL_TRACE_FUNC_BEGIN();
145
146 SkAutoCanvasRestore autoRestore(&canvas, true);
147 canvas.resetMatrix();
148
149 ReattachCachedImage(canvas);
150
151 if (cacheType_ == CacheType::CACHE_TYPE_NONE) {
152 // The cache is expired, take a snapshot again.
153 TakeSnapshot(canvas, filter);
154 ClipVisibleRect(canvas);
155 DrawCachedSnapshot(canvas, filter);
156 RS_OPTIONAL_TRACE_FUNC_END();
157 return;
158 }
159
160 // Update the cache age, this will ensure that an old cache will be invalidated immediately when intersecting with
161 // dirty region.
162 if (cacheUpdateInterval_ > 0) {
163 --cacheUpdateInterval_;
164 }
165 if (cacheType_ == CacheType::CACHE_TYPE_FILTERED_SNAPSHOT) {
166 // We are caching a filtered snapshot, draw the cached filtered image directly.
167 ClipVisibleRect(canvas);
168 DrawCachedFilteredSnapshot(canvas);
169 RS_OPTIONAL_TRACE_FUNC_END();
170 return;
171 }
172
173 // cacheType_ == CacheType::CACHE_TYPE_SNAPSHOT
174 // We are caching a snapshot, check if we should convert it to a filtered snapshot.
175 auto filterHash = filter->Hash();
176 if (filterHash == cachedFilterHash_ && filterRegion_ == clipIBounds) {
177 // Both filter and filterRegion have not changed, increase the counter.
178 frameSinceLastFilterChange_++;
179 } else {
180 // Filter or filterRegion changed, reset the counter.
181 frameSinceLastFilterChange_ = 0;
182 filterRegion_ = clipIBounds;
183 if (filterHash != cachedFilterHash_) {
184 filter->PreProcess(cachedImage_);
185 cachedFilterHash_ = filterHash;
186 }
187 }
188 // filter has not changed for more than 3 frames, convert the cache image to a filtered image.
189 if (frameSinceLastFilterChange_ >= 3) {
190 ROSEN_LOGD(
191 "RSFilterCacheManager::DrawFilter The filter filter and region have not changed in the last 3 frames, "
192 "generating a filtered image cache.");
193 GenerateFilteredSnapshot(canvas, filter);
194 ClipVisibleRect(canvas);
195 DrawCachedFilteredSnapshot(canvas);
196 RS_OPTIONAL_TRACE_FUNC_END();
197 return;
198 }
199
200 ClipVisibleRect(canvas);
201 DrawCachedSnapshot(canvas, filter);
202 RS_OPTIONAL_TRACE_FUNC_END();
203 }
204
GeneratedCachedEffectData(RSPaintFilterCanvas & canvas,const std::shared_ptr<RSSkiaFilter> & filter)205 CachedEffectData RSFilterCacheManager::GeneratedCachedEffectData(
206 RSPaintFilterCanvas& canvas, const std::shared_ptr<RSSkiaFilter>& filter)
207 {
208 // This function is similar to RSFilterCacheManager::DrawFilter, but does not draw anything on the canvas. Instead,
209 // it directly returns the cached image and region. Filter validation is not needed, since it's already done in
210 // RSPropertiesPainter::GenerateCachedEffectData.
211 RS_OPTIONAL_TRACE_FUNC_BEGIN();
212
213 ReattachCachedImage(canvas);
214
215 if (cacheType_ == CacheType::CACHE_TYPE_NONE) {
216 // The cache is expired, so take an image snapshot again and cache it.
217 ROSEN_LOGD("RSFilterCacheManager::GeneratedCachedEffectData Cache expired, taking snapshot.");
218 TakeSnapshot(canvas, filter);
219 }
220
221 // The GeneratedCachedEffectData function generates a filtered image cache, but it does not use any cache policies
222 // like DrawFilter.
223 if (cacheType_ == CacheType::CACHE_TYPE_SNAPSHOT) {
224 ROSEN_LOGD(
225 "RSFilterCacheManager::GeneratedCachedEffectData Cache is snapshot, generating filtered image cache.");
226 filterRegion_ = cachedImageRegion_;
227 GenerateFilteredSnapshot(canvas, filter);
228 }
229
230 if (cacheType_ != CacheType::CACHE_TYPE_FILTERED_SNAPSHOT) {
231 ROSEN_LOGE("RSFilterCacheManager::GeneratedCachedEffectData Cache generation failed.");
232 RS_OPTIONAL_TRACE_FUNC_END();
233 return {};
234 }
235
236 RS_OPTIONAL_TRACE_FUNC_END();
237 return { cachedImage_, cachedImageRegion_ };
238 }
239
TakeSnapshot(RSPaintFilterCanvas & canvas,const std::shared_ptr<RSSkiaFilter> & filter)240 void RSFilterCacheManager::TakeSnapshot(RSPaintFilterCanvas& canvas, const std::shared_ptr<RSSkiaFilter>& filter)
241 {
242 auto skSurface = canvas.GetSurface();
243 if (skSurface == nullptr) {
244 return;
245 }
246 RS_OPTIONAL_TRACE_FUNC_BEGIN();
247
248 auto clipIBounds = canvas.getDeviceClipBounds();
249 auto snapshotIBounds = clipIBounds;
250 bool isLargeArea = IsLargeArea(clipIBounds.width(), clipIBounds.height());
251 if (isLargeArea) {
252 // If the filter region is smaller than the threshold, we will not increase the cache update interval.
253 // To reduce the chance of cache invalidation caused by small movements, we expand the snapshot region by 5
254 // pixels.
255 snapshotIBounds.outset(5, 5); // expand the snapshot region by 5 pixels.
256 // Make sure the clipIPadding is not larger than the canvas or screen size.
257 snapshotIBounds.intersect(SkIRect::MakeSize(canvas.getBaseLayerSize()));
258 }
259
260 // Take a screenshot.
261 cachedImage_ = skSurface->makeImageSnapshot(snapshotIBounds);
262 if (cachedImage_ == nullptr) {
263 ROSEN_LOGE("RSFilterCacheManager::TakeSnapshot failed to make an image snapshot.");
264 RS_OPTIONAL_TRACE_FUNC_END();
265 return;
266 }
267 if (RSSystemProperties::GetImageGpuResourceCacheEnable(cachedImage_->width(), cachedImage_->height())) {
268 ROSEN_LOGD("TakeSnapshot cache image resource(width:%d, height:%d).",
269 cachedImage_->width(), cachedImage_->height());
270 as_IB(cachedImage_)->hintCacheGpuResource();
271 }
272 filter->PreProcess(cachedImage_);
273
274 // Update the cache state.
275 cacheType_ = CacheType::CACHE_TYPE_SNAPSHOT;
276 filterRegion_ = clipIBounds;
277 cachedFilterHash_ = filter->Hash();
278 cachedImageRegion_ = snapshotIBounds;
279 frameSinceLastFilterChange_ = 0;
280
281 // If the cached image is larger than threshold, we will increase the cache update interval, which is configurable
282 // by `hdc shell param set persist.sys.graphic.filterCacheUpdateInterval <interval>`, the default value is 1.
283 // Update: we also considered the filter parameters, only enable skip-frame if the blur radius is large enough.
284 // Note: the cache will be invalidated immediately if the cached region cannot fully cover the filter region.
285 cacheUpdateInterval_ =
286 isLargeArea && filter->CanSkipFrame() ? RSSystemProperties::GetFilterCacheUpdateInterval() : 0;
287 RS_OPTIONAL_TRACE_FUNC_END();
288 }
289
GenerateFilteredSnapshot(RSPaintFilterCanvas & canvas,const std::shared_ptr<RSSkiaFilter> & filter)290 void RSFilterCacheManager::GenerateFilteredSnapshot(
291 RSPaintFilterCanvas& canvas, const std::shared_ptr<RSSkiaFilter>& filter)
292 {
293 auto surface = canvas.GetSurface();
294 if (cacheType_ != CacheType::CACHE_TYPE_SNAPSHOT || surface == nullptr) {
295 return;
296 }
297 // The cache type has been validated, so filterRegion_ and cachedImage_ should be valid. There is no need to check
298 // them again.
299 RS_OPTIONAL_TRACE_FUNC_BEGIN();
300
301 // Create an offscreen canvas with the same size as the filter region.
302 auto offscreenSurface = surface->makeSurface(filterRegion_.width(), filterRegion_.height());
303 RSPaintFilterCanvas offscreenCanvas(offscreenSurface.get());
304
305 // Align the offscreen canvas coordinate system to the filter region.
306 offscreenCanvas.translate(-SkIntToScalar(filterRegion_.x()), -SkIntToScalar(filterRegion_.y()));
307
308 // Draw the cached snapshot onto the offscreen canvas, applying the filter.
309 DrawCachedSnapshot(offscreenCanvas, filter);
310
311 // Update the cache state with the filtered snapshot.
312 cacheType_ = CacheType::CACHE_TYPE_FILTERED_SNAPSHOT;
313 cachedImage_ = offscreenSurface->makeImageSnapshot();
314 if (RSSystemProperties::GetImageGpuResourceCacheEnable(cachedImage_->width(), cachedImage_->height())) {
315 ROSEN_LOGD("GenerateFilteredSnapshot cache image resource(width:%d, height:%d).",
316 cachedImage_->width(), cachedImage_->height());
317 as_IB(cachedImage_)->hintCacheGpuResource();
318 }
319 cachedImageRegion_ = filterRegion_;
320 RS_OPTIONAL_TRACE_FUNC_END();
321 }
322
DrawCachedSnapshot(RSPaintFilterCanvas & canvas,const std::shared_ptr<RSSkiaFilter> & filter) const323 void RSFilterCacheManager::DrawCachedSnapshot(
324 RSPaintFilterCanvas& canvas, const std::shared_ptr<RSSkiaFilter>& filter) const
325 {
326 if (cacheType_ != CacheType::CACHE_TYPE_SNAPSHOT) {
327 return;
328 }
329 RS_OPTIONAL_TRACE_FUNC_BEGIN();
330
331 // The cache type has been validated, so filterRegion_, cachedImage_, and cachedImageRegion_ should be valid. There
332 // is no need to check them again.
333 auto dstRect = SkRect::Make(filterRegion_);
334
335 // Shrink the srcRect by 1px to avoid edge artifacts, and align it to the cachedImage_ coordinate system.
336 auto srcRect = dstRect.makeOutset(-1.0f, -1.0f);
337 srcRect.offset(-SkIntToScalar(cachedImageRegion_.x()), -SkIntToScalar(cachedImageRegion_.y()));
338
339 filter->DrawImageRect(canvas, cachedImage_, srcRect, dstRect);
340 filter->PostProcess(canvas);
341 RS_OPTIONAL_TRACE_FUNC_END();
342 }
343
DrawCachedFilteredSnapshot(RSPaintFilterCanvas & canvas) const344 void RSFilterCacheManager::DrawCachedFilteredSnapshot(RSPaintFilterCanvas& canvas) const
345 {
346 if (cacheType_ != CacheType::CACHE_TYPE_FILTERED_SNAPSHOT) {
347 return;
348 }
349 RS_OPTIONAL_TRACE_FUNC_BEGIN();
350
351 // The cache type has been validated, so filterRegion_ and cachedImage_ should be valid. There is no need to check
352 // them again.
353 auto dstRect = SkRect::Make(filterRegion_);
354
355 SkPaint paint;
356 paint.setAntiAlias(true);
357 #ifdef NEW_SKIA
358 canvas.drawImageRect(cachedImage_, dstRect, SkSamplingOptions(), &paint);
359 #endif
360 RS_OPTIONAL_TRACE_FUNC_END();
361 }
362
InvalidateCache()363 void RSFilterCacheManager::InvalidateCache()
364 {
365 cacheType_ = CacheType::CACHE_TYPE_NONE;
366 cachedFilterHash_ = 0;
367 cachedImage_.reset();
368 cacheUpdateInterval_ = 0;
369 frameSinceLastFilterChange_ = 0;
370 }
371
ClipVisibleRect(RSPaintFilterCanvas & canvas) const372 void RSFilterCacheManager::ClipVisibleRect(RSPaintFilterCanvas& canvas) const
373 {
374 auto visibleIRect = canvas.GetVisibleRect().round();
375 auto deviceClipRect = canvas.getDeviceClipBounds();
376 if (!visibleIRect.isEmpty() && deviceClipRect.intersect(visibleIRect)) {
377 #ifdef NEW_SKIA
378 canvas.clipIRect(visibleIRect);
379 #endif
380 }
381 }
382
GetCachedImageRegion() const383 const SkIRect& RSFilterCacheManager::GetCachedImageRegion() const
384 {
385 return cachedImageRegion_;
386 }
387
ReattachCachedImage(RSPaintFilterCanvas & canvas)388 void RSFilterCacheManager::ReattachCachedImage(RSPaintFilterCanvas& canvas)
389 {
390 #if defined(NEW_SKIA)
391 if (cacheType_ == CacheType::CACHE_TYPE_NONE || cachedImage_->isValid(canvas.recordingContext())) {
392 #else
393 if (cacheType_ == CacheType::CACHE_TYPE_NONE || cachedImage_->isValid(canvas.getGrContext())) {
394 #endif
395 return;
396 }
397 RS_OPTIONAL_TRACE_FUNC_BEGIN();
398
399 #ifdef RS_ENABLE_GL
400 auto sharedBackendTexture = cachedImage_->getBackendTexture(false);
401 if (!sharedBackendTexture.isValid()) {
402 ROSEN_LOGE("RSFilterCacheManager::ReattachCachedImage failed to get backend texture.");
403 InvalidateCache();
404 RS_OPTIONAL_TRACE_FUNC_END();
405 return;
406 }
407 #if defined(NEW_SKIA)
408 auto reattachedCachedImage = SkImage::MakeFromTexture(canvas.recordingContext(), sharedBackendTexture,
409 #else
410 auto reattachedCachedImage = SkImage::MakeFromTexture(canvas.getGrContext(), sharedBackendTexture,
411 #endif
412 kBottomLeft_GrSurfaceOrigin, cachedImage_->colorType(), cachedImage_->alphaType(), nullptr);
413 #if defined(NEW_SKIA)
414 if (reattachedCachedImage == nullptr || !reattachedCachedImage->isValid(canvas.recordingContext())) {
415 #else
416 if (reattachedCachedImage == nullptr || !reattachedCachedImage->isValid(canvas.getGrContext())) {
417 #endif
418 ROSEN_LOGE("RSFilterCacheManager::ReattachCachedImage failed to create SkImage from backend texture.");
419 InvalidateCache();
420 RS_OPTIONAL_TRACE_FUNC_END();
421 return;
422 }
423 cachedImage_ = reattachedCachedImage;
424 #else
425 InvalidateCache();
426 #endif
427 RS_OPTIONAL_TRACE_FUNC_END();
428 }
429 } // namespace Rosen
430 } // namespace OHOS
431 #endif
432