Skip to content

Commit

Permalink
Fix child caching in opacity_layer (flutter#17914)
Browse files Browse the repository at this point in the history
Choose a child more likely to remain stable from frame to frame as the target to cache in the OpacityLayer.
  • Loading branch information
flar authored May 29, 2020
1 parent 17737e6 commit efe3f45
Show file tree
Hide file tree
Showing 9 changed files with 419 additions and 55 deletions.
13 changes: 11 additions & 2 deletions flow/layers/opacity_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
child_matrix = RasterCache::GetIntegralTransCTM(child_matrix);
#endif
TryToPrepareRasterCache(context, container, child_matrix);
TryToPrepareRasterCache(context, GetCacheableChild(), child_matrix);
}

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

if (context.raster_cache &&
context.raster_cache->Draw(GetChildContainer(),
context.raster_cache->Draw(GetCacheableChild(),
*context.leaf_nodes_canvas, &paint)) {
return;
}
Expand Down Expand Up @@ -118,4 +118,13 @@ ContainerLayer* OpacityLayer::GetChildContainer() const {
return static_cast<ContainerLayer*>(layers()[0].get());
}

Layer* OpacityLayer::GetCacheableChild() const {
ContainerLayer* child_container = GetChildContainer();
if (child_container->layers().size() == 1) {
return child_container->layers()[0].get();
}

return child_container;
}

} // namespace flutter
50 changes: 50 additions & 0 deletions flow/layers/opacity_layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,58 @@ class OpacityLayer : public ContainerLayer {
#endif // defined(OS_FUCHSIA)

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

/**
* @brief Returns the best choice for a Layer object that can be used
* in RasterCache operations to cache the children of the OpacityLayer.
*
* The returned Layer must represent all children and try to remain stable
* if the OpacityLayer is reconstructed in subsequent frames of the scene.
*
* Note that since the synthetic child container returned from the
* GetChildContainer() method is created fresh with each new OpacityLayer,
* its return value will not be a good candidate for caching. But if the
* standard recommendations for animations are followed and the child widget
* is wrapped with a RepaintBoundary widget at the framework level, then
* the synthetic child container should contain the same single child layer
* on each frame. Under those conditions, that single child of the child
* container will be the best candidate for caching in the RasterCache
* and this method will return that single child if possible to improve
* the performance of caching the children.
*
* Note that if GetCacheableChild() does not find a single stable child of
* the child container it will return the child container as a fallback.
* Even though that child is new in each frame of an animation and thus we
* cannot reuse the cached layer raster between animation frames, the single
* container child will allow us to paint the child onto an offscreen buffer
* during Preroll() which reduces one render target switch compared to
* painting the child on the fly via an AutoSaveLayer in Paint() and thus
* still improves our performance.
*
* @see GetChildContainer()
* @return the best candidate Layer for caching the children
*/
Layer* GetCacheableChild() const;

SkAlpha alpha_;
SkPoint offset_;
SkRRect frameRRect_;
Expand Down
65 changes: 65 additions & 0 deletions flow/layers/opacity_layer_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,71 @@ TEST_F(OpacityLayerTest, PaintBeforePreollDies) {
}
#endif

TEST_F(OpacityLayerTest, ChildIsCached) {
const SkAlpha alpha_half = 255 / 2;
auto initial_transform = SkMatrix::MakeTrans(50.0, 25.5);
auto other_transform = SkMatrix::MakeScale(1.0, 2.0);
const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
auto mock_layer = std::make_shared<MockLayer>(child_path);
auto layer =
std::make_shared<OpacityLayer>(alpha_half, SkPoint::Make(0.0f, 0.0f));
layer->Add(mock_layer);

SkMatrix cache_ctm = initial_transform;
SkCanvas cache_canvas;
cache_canvas.setMatrix(cache_ctm);
SkCanvas other_canvas;
other_canvas.setMatrix(other_transform);

use_mock_raster_cache();

EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
EXPECT_FALSE(raster_cache()->Draw(mock_layer.get(), other_canvas));
EXPECT_FALSE(raster_cache()->Draw(mock_layer.get(), cache_canvas));

layer->Preroll(preroll_context(), initial_transform);

EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);
EXPECT_FALSE(raster_cache()->Draw(mock_layer.get(), other_canvas));
EXPECT_TRUE(raster_cache()->Draw(mock_layer.get(), cache_canvas));
}

TEST_F(OpacityLayerTest, ChildrenNotCached) {
const SkAlpha alpha_half = 255 / 2;
auto initial_transform = SkMatrix::MakeTrans(50.0, 25.5);
auto other_transform = SkMatrix::MakeScale(1.0, 2.0);
const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
const SkPath child_path2 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
auto mock_layer1 = std::make_shared<MockLayer>(child_path1);
auto mock_layer2 = std::make_shared<MockLayer>(child_path2);
auto layer =
std::make_shared<OpacityLayer>(alpha_half, SkPoint::Make(0.0f, 0.0f));
layer->Add(mock_layer1);
layer->Add(mock_layer2);

SkMatrix cache_ctm = initial_transform;
SkCanvas cache_canvas;
cache_canvas.setMatrix(cache_ctm);
SkCanvas other_canvas;
other_canvas.setMatrix(other_transform);

use_mock_raster_cache();

EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), other_canvas));
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), cache_canvas));
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), other_canvas));
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), cache_canvas));

layer->Preroll(preroll_context(), initial_transform);

EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), other_canvas));
EXPECT_FALSE(raster_cache()->Draw(mock_layer1.get(), cache_canvas));
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), other_canvas));
EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), cache_canvas));
}

TEST_F(OpacityLayerTest, FullyOpaque) {
const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f);
Expand Down
103 changes: 61 additions & 42 deletions flow/raster_cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ static bool IsPictureWorthRasterizing(SkPicture* picture,
}

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

if (!surface) {
return {};
return nullptr;
}

SkCanvas* canvas = surface->getCanvas();
Expand All @@ -118,14 +118,16 @@ static RasterCacheResult Rasterize(
DrawCheckerboard(canvas, logical_rect);
}

return {surface->makeImageSnapshot(), logical_rect};
return std::make_unique<RasterCacheResult>(surface->makeImageSnapshot(),
logical_rect);
}

RasterCacheResult RasterizePicture(SkPicture* picture,
GrContext* context,
const SkMatrix& ctm,
SkColorSpace* dst_color_space,
bool checkerboard) {
std::unique_ptr<RasterCacheResult> RasterCache::RasterizePicture(
SkPicture* picture,
GrContext* context,
const SkMatrix& ctm,
SkColorSpace* dst_color_space,
bool checkerboard) const {
return Rasterize(context, ctm, dst_color_space, checkerboard,
picture->cullRect(),
[=](SkCanvas* canvas) { canvas->drawPicture(picture); });
Expand All @@ -138,34 +140,41 @@ void RasterCache::Prepare(PrerollContext* context,
Entry& entry = layer_cache_[cache_key];
entry.access_count++;
entry.used_this_frame = true;
if (!entry.image.is_valid()) {
entry.image = Rasterize(
context->gr_context, ctm, context->dst_color_space,
checkerboard_images_, layer->paint_bounds(),
[layer, context](SkCanvas* canvas) {
SkISize canvas_size = canvas->getBaseLayerSize();
SkNWayCanvas internal_nodes_canvas(canvas_size.width(),
canvas_size.height());
internal_nodes_canvas.addCanvas(canvas);
Layer::PaintContext paintContext = {
(SkCanvas*)&internal_nodes_canvas,
canvas,
context->gr_context,
nullptr,
context->raster_time,
context->ui_time,
context->texture_registry,
context->has_platform_view ? nullptr : context->raster_cache,
context->checkerboard_offscreen_layers,
context->frame_physical_depth,
context->frame_device_pixel_ratio};
if (layer->needs_painting()) {
layer->Paint(paintContext);
}
});
if (!entry.image) {
entry.image = RasterizeLayer(context, layer, ctm, checkerboard_images_);
}
}

std::unique_ptr<RasterCacheResult> RasterCache::RasterizeLayer(
PrerollContext* context,
Layer* layer,
const SkMatrix& ctm,
bool checkerboard) const {
return Rasterize(
context->gr_context, ctm, context->dst_color_space, checkerboard,
layer->paint_bounds(), [layer, context](SkCanvas* canvas) {
SkISize canvas_size = canvas->getBaseLayerSize();
SkNWayCanvas internal_nodes_canvas(canvas_size.width(),
canvas_size.height());
internal_nodes_canvas.addCanvas(canvas);
Layer::PaintContext paintContext = {
(SkCanvas*)&internal_nodes_canvas, // internal_nodes_canvas
canvas, // leaf_nodes_canvas
context->gr_context, // gr_context
nullptr, // view_embedder
context->raster_time,
context->ui_time,
context->texture_registry,
context->has_platform_view ? nullptr : context->raster_cache,
context->checkerboard_offscreen_layers,
context->frame_physical_depth,
context->frame_device_pixel_ratio};
if (layer->needs_painting()) {
layer->Paint(paintContext);
}
});
}

bool RasterCache::Prepare(GrContext* context,
SkPicture* picture,
const SkMatrix& transformation_matrix,
Expand Down Expand Up @@ -202,7 +211,7 @@ bool RasterCache::Prepare(GrContext* context,
return false;
}

if (!entry.image.is_valid()) {
if (!entry.image) {
entry.image = RasterizePicture(picture, context, transformation_matrix,
dst_color_space, checkerboard_images_);
picture_cached_this_frame_++;
Expand All @@ -221,8 +230,8 @@ bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const {
entry.access_count++;
entry.used_this_frame = true;

if (entry.image.is_valid()) {
entry.image.draw(canvas);
if (entry.image) {
entry.image->draw(canvas);
return true;
}

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

if (entry.image.is_valid()) {
entry.image.draw(canvas, paint);
if (entry.image) {
entry.image->draw(canvas, paint);
return true;
}

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

size_t RasterCache::GetLayerCachedEntriesCount() const {
return layer_cache_.size();
}

size_t RasterCache::GetPictureCachedEntriesCount() const {
return picture_cache_.size();
}

void RasterCache::SetCheckboardCacheImages(bool checkerboard) {
if (checkerboard_images_ == checkerboard) {
return;
Expand All @@ -287,15 +304,17 @@ void RasterCache::TraceStatsToTimeline() const {
size_t picture_cache_bytes = 0;

for (const auto& item : layer_cache_) {
const auto dimensions = item.second.image.image_dimensions();
layer_cache_count++;
layer_cache_bytes += dimensions.width() * dimensions.height() * 4;
if (item.second.image) {
layer_cache_bytes += item.second.image->image_bytes();
}
}

for (const auto& item : picture_cache_) {
const auto dimensions = item.second.image.image_dimensions();
picture_cache_count++;
picture_cache_bytes += dimensions.width() * dimensions.height() * 4;
if (item.second.image) {
picture_cache_bytes += item.second.image->image_bytes();
}
}

FML_TRACE_COUNTER("flutter", "RasterCache",
Expand Down
Loading

0 comments on commit efe3f45

Please sign in to comment.