Skip to content

Commit efe3f45

Browse files
authored
Fix child caching in opacity_layer (flutter#17914)
Choose a child more likely to remain stable from frame to frame as the target to cache in the OpacityLayer.
1 parent 17737e6 commit efe3f45

File tree

9 files changed

+419
-55
lines changed

9 files changed

+419
-55
lines changed

flow/layers/opacity_layer.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
5555
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
5656
child_matrix = RasterCache::GetIntegralTransCTM(child_matrix);
5757
#endif
58-
TryToPrepareRasterCache(context, container, child_matrix);
58+
TryToPrepareRasterCache(context, GetCacheableChild(), child_matrix);
5959
}
6060

6161
// Restore cull_rect
@@ -78,7 +78,7 @@ void OpacityLayer::Paint(PaintContext& context) const {
7878
#endif
7979

8080
if (context.raster_cache &&
81-
context.raster_cache->Draw(GetChildContainer(),
81+
context.raster_cache->Draw(GetCacheableChild(),
8282
*context.leaf_nodes_canvas, &paint)) {
8383
return;
8484
}
@@ -118,4 +118,13 @@ ContainerLayer* OpacityLayer::GetChildContainer() const {
118118
return static_cast<ContainerLayer*>(layers()[0].get());
119119
}
120120

121+
Layer* OpacityLayer::GetCacheableChild() const {
122+
ContainerLayer* child_container = GetChildContainer();
123+
if (child_container->layers().size() == 1) {
124+
return child_container->layers()[0].get();
125+
}
126+
127+
return child_container;
128+
}
129+
121130
} // namespace flutter

flow/layers/opacity_layer.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,58 @@ class OpacityLayer : public ContainerLayer {
3838
#endif // defined(OS_FUCHSIA)
3939

4040
private:
41+
/**
42+
* @brief Returns the ContainerLayer used to hold all of the children
43+
* of the OpacityLayer.
44+
*
45+
* Often opacity layers will only have a single child since the associated
46+
* Flutter widget is specified with only a single child widget pointer.
47+
* But depending on the structure of the child tree that single widget at
48+
* the framework level can turn into multiple children at the engine
49+
* API level since there is no guarantee of a 1:1 correspondence of widgets
50+
* to engine layers. This synthetic child container layer is established to
51+
* hold all of the children in a single layer so that we can cache their
52+
* output, but this synthetic layer will typically not be the best choice
53+
* for the layer cache since the synthetic container is created fresh with
54+
* each new OpacityLayer, and so may not be stable from frame to frame.
55+
*
56+
* @see GetCacheableChild()
57+
* @return the ContainerLayer child used to hold the children
58+
*/
4159
ContainerLayer* GetChildContainer() const;
4260

61+
/**
62+
* @brief Returns the best choice for a Layer object that can be used
63+
* in RasterCache operations to cache the children of the OpacityLayer.
64+
*
65+
* The returned Layer must represent all children and try to remain stable
66+
* if the OpacityLayer is reconstructed in subsequent frames of the scene.
67+
*
68+
* Note that since the synthetic child container returned from the
69+
* GetChildContainer() method is created fresh with each new OpacityLayer,
70+
* its return value will not be a good candidate for caching. But if the
71+
* standard recommendations for animations are followed and the child widget
72+
* is wrapped with a RepaintBoundary widget at the framework level, then
73+
* the synthetic child container should contain the same single child layer
74+
* on each frame. Under those conditions, that single child of the child
75+
* container will be the best candidate for caching in the RasterCache
76+
* and this method will return that single child if possible to improve
77+
* the performance of caching the children.
78+
*
79+
* Note that if GetCacheableChild() does not find a single stable child of
80+
* the child container it will return the child container as a fallback.
81+
* Even though that child is new in each frame of an animation and thus we
82+
* cannot reuse the cached layer raster between animation frames, the single
83+
* container child will allow us to paint the child onto an offscreen buffer
84+
* during Preroll() which reduces one render target switch compared to
85+
* painting the child on the fly via an AutoSaveLayer in Paint() and thus
86+
* still improves our performance.
87+
*
88+
* @see GetChildContainer()
89+
* @return the best candidate Layer for caching the children
90+
*/
91+
Layer* GetCacheableChild() const;
92+
4393
SkAlpha alpha_;
4494
SkPoint offset_;
4595
SkRRect frameRRect_;

flow/layers/opacity_layer_unittests.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,71 @@ TEST_F(OpacityLayerTest, PaintBeforePreollDies) {
5353
}
5454
#endif
5555

56+
TEST_F(OpacityLayerTest, ChildIsCached) {
57+
const SkAlpha alpha_half = 255 / 2;
58+
auto initial_transform = SkMatrix::MakeTrans(50.0, 25.5);
59+
auto other_transform = SkMatrix::MakeScale(1.0, 2.0);
60+
const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
61+
auto mock_layer = std::make_shared<MockLayer>(child_path);
62+
auto layer =
63+
std::make_shared<OpacityLayer>(alpha_half, SkPoint::Make(0.0f, 0.0f));
64+
layer->Add(mock_layer);
65+
66+
SkMatrix cache_ctm = initial_transform;
67+
SkCanvas cache_canvas;
68+
cache_canvas.setMatrix(cache_ctm);
69+
SkCanvas other_canvas;
70+
other_canvas.setMatrix(other_transform);
71+
72+
use_mock_raster_cache();
73+
74+
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
75+
EXPECT_FALSE(raster_cache()->Draw(mock_layer.get(), other_canvas));
76+
EXPECT_FALSE(raster_cache()->Draw(mock_layer.get(), cache_canvas));
77+
78+
layer->Preroll(preroll_context(), initial_transform);
79+
80+
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);
81+
EXPECT_FALSE(raster_cache()->Draw(mock_layer.get(), other_canvas));
82+
EXPECT_TRUE(raster_cache()->Draw(mock_layer.get(), cache_canvas));
83+
}
84+
85+
TEST_F(OpacityLayerTest, ChildrenNotCached) {
86+
const SkAlpha alpha_half = 255 / 2;
87+
auto initial_transform = SkMatrix::MakeTrans(50.0, 25.5);
88+
auto other_transform = SkMatrix::MakeScale(1.0, 2.0);
89+
const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
90+
const SkPath child_path2 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
91+
auto mock_layer1 = std::make_shared<MockLayer>(child_path1);
92+
auto mock_layer2 = std::make_shared<MockLayer>(child_path2);
93+
auto layer =
94+
std::make_shared<OpacityLayer>(alpha_half, SkPoint::Make(0.0f, 0.0f));
95+
layer->Add(mock_layer1);
96+
layer->Add(mock_layer2);
97+
98+
SkMatrix cache_ctm = initial_transform;
99+
SkCanvas cache_canvas;
100+
cache_canvas.setMatrix(cache_ctm);
101+
SkCanvas other_canvas;
102+
other_canvas.setMatrix(other_transform);
103+
104+
use_mock_raster_cache();
105+
106+
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
107+
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), other_canvas));
108+
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), cache_canvas));
109+
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), other_canvas));
110+
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), cache_canvas));
111+
112+
layer->Preroll(preroll_context(), initial_transform);
113+
114+
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);
115+
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), other_canvas));
116+
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), cache_canvas));
117+
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), other_canvas));
118+
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), cache_canvas));
119+
}
120+
56121
TEST_F(OpacityLayerTest, FullyOpaque) {
57122
const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
58123
const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f);

flow/raster_cache.cc

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ static bool IsPictureWorthRasterizing(SkPicture* picture,
8686
}
8787

8888
/// @note Procedure doesn't copy all closures.
89-
static RasterCacheResult Rasterize(
89+
static std::unique_ptr<RasterCacheResult> Rasterize(
9090
GrContext* context,
9191
const SkMatrix& ctm,
9292
SkColorSpace* dst_color_space,
@@ -105,7 +105,7 @@ static RasterCacheResult Rasterize(
105105
: SkSurface::MakeRaster(image_info);
106106

107107
if (!surface) {
108-
return {};
108+
return nullptr;
109109
}
110110

111111
SkCanvas* canvas = surface->getCanvas();
@@ -118,14 +118,16 @@ static RasterCacheResult Rasterize(
118118
DrawCheckerboard(canvas, logical_rect);
119119
}
120120

121-
return {surface->makeImageSnapshot(), logical_rect};
121+
return std::make_unique<RasterCacheResult>(surface->makeImageSnapshot(),
122+
logical_rect);
122123
}
123124

124-
RasterCacheResult RasterizePicture(SkPicture* picture,
125-
GrContext* context,
126-
const SkMatrix& ctm,
127-
SkColorSpace* dst_color_space,
128-
bool checkerboard) {
125+
std::unique_ptr<RasterCacheResult> RasterCache::RasterizePicture(
126+
SkPicture* picture,
127+
GrContext* context,
128+
const SkMatrix& ctm,
129+
SkColorSpace* dst_color_space,
130+
bool checkerboard) const {
129131
return Rasterize(context, ctm, dst_color_space, checkerboard,
130132
picture->cullRect(),
131133
[=](SkCanvas* canvas) { canvas->drawPicture(picture); });
@@ -138,34 +140,41 @@ void RasterCache::Prepare(PrerollContext* context,
138140
Entry& entry = layer_cache_[cache_key];
139141
entry.access_count++;
140142
entry.used_this_frame = true;
141-
if (!entry.image.is_valid()) {
142-
entry.image = Rasterize(
143-
context->gr_context, ctm, context->dst_color_space,
144-
checkerboard_images_, layer->paint_bounds(),
145-
[layer, context](SkCanvas* canvas) {
146-
SkISize canvas_size = canvas->getBaseLayerSize();
147-
SkNWayCanvas internal_nodes_canvas(canvas_size.width(),
148-
canvas_size.height());
149-
internal_nodes_canvas.addCanvas(canvas);
150-
Layer::PaintContext paintContext = {
151-
(SkCanvas*)&internal_nodes_canvas,
152-
canvas,
153-
context->gr_context,
154-
nullptr,
155-
context->raster_time,
156-
context->ui_time,
157-
context->texture_registry,
158-
context->has_platform_view ? nullptr : context->raster_cache,
159-
context->checkerboard_offscreen_layers,
160-
context->frame_physical_depth,
161-
context->frame_device_pixel_ratio};
162-
if (layer->needs_painting()) {
163-
layer->Paint(paintContext);
164-
}
165-
});
143+
if (!entry.image) {
144+
entry.image = RasterizeLayer(context, layer, ctm, checkerboard_images_);
166145
}
167146
}
168147

148+
std::unique_ptr<RasterCacheResult> RasterCache::RasterizeLayer(
149+
PrerollContext* context,
150+
Layer* layer,
151+
const SkMatrix& ctm,
152+
bool checkerboard) const {
153+
return Rasterize(
154+
context->gr_context, ctm, context->dst_color_space, checkerboard,
155+
layer->paint_bounds(), [layer, context](SkCanvas* canvas) {
156+
SkISize canvas_size = canvas->getBaseLayerSize();
157+
SkNWayCanvas internal_nodes_canvas(canvas_size.width(),
158+
canvas_size.height());
159+
internal_nodes_canvas.addCanvas(canvas);
160+
Layer::PaintContext paintContext = {
161+
(SkCanvas*)&internal_nodes_canvas, // internal_nodes_canvas
162+
canvas, // leaf_nodes_canvas
163+
context->gr_context, // gr_context
164+
nullptr, // view_embedder
165+
context->raster_time,
166+
context->ui_time,
167+
context->texture_registry,
168+
context->has_platform_view ? nullptr : context->raster_cache,
169+
context->checkerboard_offscreen_layers,
170+
context->frame_physical_depth,
171+
context->frame_device_pixel_ratio};
172+
if (layer->needs_painting()) {
173+
layer->Paint(paintContext);
174+
}
175+
});
176+
}
177+
169178
bool RasterCache::Prepare(GrContext* context,
170179
SkPicture* picture,
171180
const SkMatrix& transformation_matrix,
@@ -202,7 +211,7 @@ bool RasterCache::Prepare(GrContext* context,
202211
return false;
203212
}
204213

205-
if (!entry.image.is_valid()) {
214+
if (!entry.image) {
206215
entry.image = RasterizePicture(picture, context, transformation_matrix,
207216
dst_color_space, checkerboard_images_);
208217
picture_cached_this_frame_++;
@@ -221,8 +230,8 @@ bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const {
221230
entry.access_count++;
222231
entry.used_this_frame = true;
223232

224-
if (entry.image.is_valid()) {
225-
entry.image.draw(canvas);
233+
if (entry.image) {
234+
entry.image->draw(canvas);
226235
return true;
227236
}
228237

@@ -242,8 +251,8 @@ bool RasterCache::Draw(const Layer* layer,
242251
entry.access_count++;
243252
entry.used_this_frame = true;
244253

245-
if (entry.image.is_valid()) {
246-
entry.image.draw(canvas, paint);
254+
if (entry.image) {
255+
entry.image->draw(canvas, paint);
247256
return true;
248257
}
249258

@@ -266,6 +275,14 @@ size_t RasterCache::GetCachedEntriesCount() const {
266275
return layer_cache_.size() + picture_cache_.size();
267276
}
268277

278+
size_t RasterCache::GetLayerCachedEntriesCount() const {
279+
return layer_cache_.size();
280+
}
281+
282+
size_t RasterCache::GetPictureCachedEntriesCount() const {
283+
return picture_cache_.size();
284+
}
285+
269286
void RasterCache::SetCheckboardCacheImages(bool checkerboard) {
270287
if (checkerboard_images_ == checkerboard) {
271288
return;
@@ -287,15 +304,17 @@ void RasterCache::TraceStatsToTimeline() const {
287304
size_t picture_cache_bytes = 0;
288305

289306
for (const auto& item : layer_cache_) {
290-
const auto dimensions = item.second.image.image_dimensions();
291307
layer_cache_count++;
292-
layer_cache_bytes += dimensions.width() * dimensions.height() * 4;
308+
if (item.second.image) {
309+
layer_cache_bytes += item.second.image->image_bytes();
310+
}
293311
}
294312

295313
for (const auto& item : picture_cache_) {
296-
const auto dimensions = item.second.image.image_dimensions();
297314
picture_cache_count++;
298-
picture_cache_bytes += dimensions.width() * dimensions.height() * 4;
315+
if (item.second.image) {
316+
picture_cache_bytes += item.second.image->image_bytes();
317+
}
299318
}
300319

301320
FML_TRACE_COUNTER("flutter", "RasterCache",

0 commit comments

Comments
 (0)