Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0d4d2cc

Browse files
author
Jonah Williams
authored
[Impeller] better handle allocation herustics of Android slide in page transition. (#56762)
For flutter/flutter#158881 The new Android m3 page transition animates a saveLayer w/ opacity + slide in animation. See example video from linked PR: https://github.com/user-attachments/assets/1382374a-4e0c-4a0e-9d70-948ce12e6298 On each frame, we intersect the coverage rect of this saveLayer contents with the screen rect, and shrink it to a partial rectangle. But this changes each frame, which forces us to re-allocate a large texture each frame, causing performance issues. Why not ignore the cull rect entirely? We sometimes ignore the cull rect for the image filter layer for similar reasons, but from observation, the sizes of these saveLayer can be slightly larger than the screen for flutter gallery. Instead, I attempted to use the cull rect size to shrink the saveLayer by shifting the origin before intersecting. I think this should be safe to do, as I believe it could only leave the coverage as larger than it would have been and not smaller.
1 parent 3c751a9 commit 0d4d2cc

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

impeller/entity/save_layer_utils.cc

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
#include "impeller/entity/save_layer_utils.h"
6+
#include "impeller/geometry/scalar.h"
67

78
namespace impeller {
89

@@ -106,8 +107,41 @@ std::optional<Rect> ComputeSaveLayerCoverage(
106107

107108
// Transform the input coverage into the global coordinate space before
108109
// computing the bounds limit intersection.
109-
return coverage.TransformBounds(effect_transform)
110-
.Intersection(coverage_limit);
110+
Rect transformed_coverage = coverage.TransformBounds(effect_transform);
111+
std::optional<Rect> intersection =
112+
transformed_coverage.Intersection(coverage_limit);
113+
if (!intersection.has_value()) {
114+
return std::nullopt;
115+
}
116+
117+
// Sometimes a saveLayer is only slightly shifted outside of the cull rect,
118+
// but is being animated in. This is common for the Android slide in page
119+
// transitions. In these cases, computing a cull that is too tight can cause
120+
// thrashing of the texture cache. Instead, we try to determine the
121+
// intersection using only the sizing by shifting the coverage rect into the
122+
// cull rect origin.
123+
Point delta = coverage_limit.GetOrigin() - transformed_coverage.GetOrigin();
124+
125+
// This herustic is limited to perfectly vertical or horizontal transitions
126+
// that slide in, limited to a fixed threshold of ~30%. This value is based on
127+
// the Android slide in page transition which experimental has threshold
128+
// values of up to 28%.
129+
static constexpr Scalar kThresholdLimit = 0.3;
130+
131+
if (ScalarNearlyEqual(delta.y, 0) || ScalarNearlyEqual(delta.x, 0)) {
132+
Scalar threshold = std::max(std::abs(delta.x / coverage_limit.GetWidth()),
133+
std::abs(delta.y / coverage_limit.GetHeight()));
134+
if (threshold < kThresholdLimit) {
135+
std::optional<Rect> shifted_intersected_value =
136+
transformed_coverage.Shift(delta).Intersection(coverage_limit);
137+
138+
if (shifted_intersected_value.has_value()) {
139+
return shifted_intersected_value.value().Shift(-delta);
140+
}
141+
}
142+
}
143+
144+
return intersection;
111145
}
112146

113147
} // namespace impeller

impeller/entity/save_layer_utils_unittests.cc

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,79 @@ TEST(SaveLayerUtilsTest,
280280
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 50, 50));
281281
}
282282

283+
TEST(SaveLayerUtilsTest,
284+
PartiallyIntersectingCoverageIgnoresOriginWithSlideSemanticsX) {
285+
// X varies, translation is performed on coverage.
286+
auto coverage = ComputeSaveLayerCoverage(
287+
/*content_coverage=*/Rect::MakeLTRB(5, 0, 210, 210), //
288+
/*effect_transform=*/{}, //
289+
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
290+
/*image_filter=*/nullptr //
291+
);
292+
293+
ASSERT_TRUE(coverage.has_value());
294+
// Size that matches coverage limit
295+
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(5, 0, 105, 100));
296+
}
297+
298+
TEST(SaveLayerUtilsTest,
299+
PartiallyIntersectingCoverageIgnoresOriginWithSlideSemanticsY) {
300+
// Y varies, translation is performed on coverage.
301+
auto coverage = ComputeSaveLayerCoverage(
302+
/*content_coverage=*/Rect::MakeLTRB(0, 5, 210, 210), //
303+
/*effect_transform=*/{}, //
304+
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
305+
/*image_filter=*/nullptr //
306+
);
307+
308+
ASSERT_TRUE(coverage.has_value());
309+
// Size that matches coverage limit
310+
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 5, 100, 105));
311+
}
312+
313+
TEST(SaveLayerUtilsTest, PartiallyIntersectingCoverageIgnoresOriginBothXAndY) {
314+
// Both X and Y vary, no transation is performed.
315+
auto coverage = ComputeSaveLayerCoverage(
316+
/*content_coverage=*/Rect::MakeLTRB(5, 5, 210, 210), //
317+
/*effect_transform=*/{}, //
318+
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
319+
/*image_filter=*/nullptr //
320+
);
321+
322+
ASSERT_TRUE(coverage.has_value());
323+
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(5, 5, 100, 100));
324+
}
325+
326+
TEST(SaveLayerUtilsTest, PartiallyIntersectingCoverageWithOveriszedThresholdY) {
327+
// Y varies, translation is not performed on coverage because it is too far
328+
// offscreen.
329+
auto coverage = ComputeSaveLayerCoverage(
330+
/*content_coverage=*/Rect::MakeLTRB(0, 80, 200, 200), //
331+
/*effect_transform=*/{}, //
332+
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
333+
/*image_filter=*/nullptr //
334+
);
335+
336+
ASSERT_TRUE(coverage.has_value());
337+
// Size that matches coverage limit
338+
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 80, 100, 100));
339+
}
340+
341+
TEST(SaveLayerUtilsTest, PartiallyIntersectingCoverageWithOveriszedThresholdX) {
342+
// X varies, translation is not performed on coverage because it is too far
343+
// offscreen.
344+
auto coverage = ComputeSaveLayerCoverage(
345+
/*content_coverage=*/Rect::MakeLTRB(80, 0, 200, 200), //
346+
/*effect_transform=*/{}, //
347+
/*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), //
348+
/*image_filter=*/nullptr //
349+
);
350+
351+
ASSERT_TRUE(coverage.has_value());
352+
// Size that matches coverage limit
353+
EXPECT_EQ(coverage.value(), Rect::MakeLTRB(80, 0, 100, 100));
354+
}
355+
283356
} // namespace testing
284357
} // namespace impeller
285358

0 commit comments

Comments
 (0)