1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #ifndef FLUTTER_FLOW_RASTER_CACHE_UTIL_H_ |
6 | #define FLUTTER_FLOW_RASTER_CACHE_UTIL_H_ |
7 | |
8 | #include "flutter/fml/logging.h" |
9 | #include "include/core/SkM44.h" |
10 | #include "include/core/SkMatrix.h" |
11 | #include "include/core/SkRect.h" |
12 | |
13 | namespace flutter { |
14 | |
15 | struct RasterCacheUtil { |
16 | // The default max number of picture and display list raster caches to be |
17 | // generated per frame. Generating too many caches in one frame may cause jank |
18 | // on that frame. This limit allows us to throttle the cache and distribute |
19 | // the work across multiple frames. |
20 | static constexpr int kDefaultPictureAndDisplayListCacheLimitPerFrame = 3; |
21 | |
22 | // The ImageFilterLayer might cache the filtered output of this layer |
23 | // if the layer remains stable (if it is not animating for instance). |
24 | // If the ImageFilterLayer is not the same between rendered frames, |
25 | // though, it will cache its children instead and filter their cached |
26 | // output on the fly. |
27 | // Caching just the children saves the time to render them and also |
28 | // avoids a rendering surface switch to draw them. |
29 | // Caching the layer itself avoids all of that and additionally avoids |
30 | // the cost of applying the filter, but can be worse than caching the |
31 | // children if the filter itself is not stable from frame to frame. |
32 | // This constant controls how many times we will Preroll and Paint this |
33 | // same ImageFilterLayer before we consider the layer and filter to be |
34 | // stable enough to switch from caching the children to caching the |
35 | // filtered output of this layer. |
36 | static constexpr int kMinimumRendersBeforeCachingFilterLayer = 3; |
37 | |
38 | static bool CanRasterizeRect(const SkRect& cull_rect) { |
39 | if (cull_rect.isEmpty()) { |
40 | // No point in ever rasterizing an empty display list. |
41 | return false; |
42 | } |
43 | |
44 | if (!cull_rect.isFinite()) { |
45 | // Cannot attempt to rasterize into an infinitely large surface. |
46 | FML_LOG(INFO) << "Attempted to raster cache non-finite display list" ; |
47 | return false; |
48 | } |
49 | |
50 | return true; |
51 | } |
52 | |
53 | static SkRect GetDeviceBounds(const SkRect& rect, const SkMatrix& ctm) { |
54 | SkRect device_rect; |
55 | ctm.mapRect(dst: &device_rect, src: rect); |
56 | return device_rect; |
57 | } |
58 | |
59 | static SkRect GetRoundedOutDeviceBounds(const SkRect& rect, |
60 | const SkMatrix& ctm) { |
61 | SkRect device_rect; |
62 | ctm.mapRect(dst: &device_rect, src: rect); |
63 | device_rect.roundOut(dst: &device_rect); |
64 | return device_rect; |
65 | } |
66 | |
67 | /** |
68 | * @brief Snap the translation components of the matrix to integers. |
69 | * |
70 | * The snapping will only happen if the matrix only has scale and translation |
71 | * transformations. This is used, along with GetRoundedOutDeviceBounds, to |
72 | * ensure that the textures drawn by the raster cache are exactly aligned to |
73 | * physical pixels. Any layers that participate in raster caching must align |
74 | * themselves to physical pixels even when not cached to prevent a change in |
75 | * apparent location if caching is later applied. |
76 | * |
77 | * @param ctm the current transformation matrix. |
78 | * @return SkMatrix the snapped transformation matrix. |
79 | */ |
80 | static SkMatrix GetIntegralTransCTM(const SkMatrix& ctm) { |
81 | // Avoid integral snapping if the matrix has complex transformation to avoid |
82 | // the artifact observed in https://github.com/flutter/flutter/issues/41654. |
83 | if (!ctm.isScaleTranslate()) { |
84 | return ctm; |
85 | } |
86 | SkMatrix result = ctm; |
87 | result[SkMatrix::kMTransX] = SkScalarRoundToScalar(ctm.getTranslateX()); |
88 | result[SkMatrix::kMTransY] = SkScalarRoundToScalar(ctm.getTranslateY()); |
89 | return result; |
90 | } |
91 | |
92 | /** |
93 | * @brief Snap the translation components of the matrix to integers. |
94 | * |
95 | * The snapping will only happen if the matrix only has scale and translation |
96 | * transformations. This is used, along with GetRoundedOutDeviceBounds, to |
97 | * ensure that the textures drawn by the raster cache are exactly aligned to |
98 | * physical pixels. Any layers that participate in raster caching must align |
99 | * themselves to physical pixels even when not cached to prevent a change in |
100 | * apparent location if caching is later applied. |
101 | * |
102 | * @param ctm the current transformation matrix. |
103 | * @return SkM44 the snapped transformation matrix. |
104 | */ |
105 | static SkM44 GetIntegralTransCTM(const SkM44& ctm) { |
106 | // Avoid integral snapping if the matrix has complex transformation to avoid |
107 | // the artifact observed in https://github.com/flutter/flutter/issues/41654. |
108 | if (ctm.rc(r: 0, c: 1) != 0 || ctm.rc(r: 0, c: 2) != 0) { |
109 | // X multiplied by either Y or Z |
110 | return ctm; |
111 | } |
112 | if (ctm.rc(r: 1, c: 0) != 0 || ctm.rc(r: 1, c: 2) != 0) { |
113 | // Y multiplied by either X or Z |
114 | return ctm; |
115 | } |
116 | // We do not need to worry about the Z row unless the W row |
117 | // has perspective entries... |
118 | if (ctm.rc(r: 3, c: 0) != 0 || ctm.rc(r: 3, c: 1) != 0 || ctm.rc(r: 3, c: 2) != 0 || |
119 | ctm.rc(r: 3, c: 3) != 1) { |
120 | // W not identity row, therefore perspective is applied |
121 | return ctm; |
122 | } |
123 | |
124 | SkM44 result = ctm; |
125 | result.setRC(r: 0, c: 3, SkScalarRoundToScalar(ctm.rc(0, 3))); |
126 | result.setRC(r: 1, c: 3, SkScalarRoundToScalar(ctm.rc(1, 3))); |
127 | // No need to worry about Z translation because it has no effect |
128 | // without perspective entries... |
129 | return result; |
130 | } |
131 | }; |
132 | |
133 | } // namespace flutter |
134 | |
135 | #endif // FLUTTER_FLOW_RASTER_CACHE_UTIL_H_ |
136 | |