Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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