Fix child caching in opacity_layer (#17914)
Choose a child more likely to remain stable from frame to frame as the target to cache in the OpacityLayer.
diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc
index d864db3..9fd0bcc 100644
--- a/flow/layers/opacity_layer.cc
+++ b/flow/layers/opacity_layer.cc
@@ -55,7 +55,7 @@
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
child_matrix = RasterCache::GetIntegralTransCTM(child_matrix);
#endif
- TryToPrepareRasterCache(context, container, child_matrix);
+ TryToPrepareRasterCache(context, GetCacheableChild(), child_matrix);
}
// Restore cull_rect
@@ -78,7 +78,7 @@
#endif
if (context.raster_cache &&
- context.raster_cache->Draw(GetChildContainer(),
+ context.raster_cache->Draw(GetCacheableChild(),
*context.leaf_nodes_canvas, &paint)) {
return;
}
@@ -118,4 +118,13 @@
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
diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h
index 658dd1a..4edc61a 100644
--- a/flow/layers/opacity_layer.h
+++ b/flow/layers/opacity_layer.h
@@ -38,8 +38,58 @@
#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_;
diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc
index e08d5c9..735bb0a 100644
--- a/flow/layers/opacity_layer_unittests.cc
+++ b/flow/layers/opacity_layer_unittests.cc
@@ -53,6 +53,71 @@
}
#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);
diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc
index d41408b..89fc1eb 100644
--- a/flow/raster_cache.cc
+++ b/flow/raster_cache.cc
@@ -86,7 +86,7 @@
}
/// @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,
@@ -105,7 +105,7 @@
: SkSurface::MakeRaster(image_info);
if (!surface) {
- return {};
+ return nullptr;
}
SkCanvas* canvas = surface->getCanvas();
@@ -118,14 +118,16 @@
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); });
@@ -138,34 +140,41 @@
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,
@@ -202,7 +211,7 @@
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_++;
@@ -221,8 +230,8 @@
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;
}
@@ -242,8 +251,8 @@
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;
}
@@ -266,6 +275,14 @@
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;
@@ -287,15 +304,17 @@
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",
diff --git a/flow/raster_cache.h b/flow/raster_cache.h
index 3174ed0..a244846 100644
--- a/flow/raster_cache.h
+++ b/flow/raster_cache.h
@@ -19,22 +19,22 @@
class RasterCacheResult {
public:
- RasterCacheResult() = default;
-
- RasterCacheResult(const RasterCacheResult& other) = default;
-
RasterCacheResult(sk_sp<SkImage> image, const SkRect& logical_rect);
- operator bool() const { return static_cast<bool>(image_); }
+ virtual ~RasterCacheResult() = default;
- bool is_valid() const { return static_cast<bool>(image_); };
+ virtual void draw(SkCanvas& canvas, const SkPaint* paint = nullptr) const;
- void draw(SkCanvas& canvas, const SkPaint* paint = nullptr) const;
-
- SkISize image_dimensions() const {
+ virtual SkISize image_dimensions() const {
return image_ ? image_->dimensions() : SkISize::Make(0, 0);
};
+ virtual int64_t image_bytes() const {
+ return image_ ? image_->dimensions().area() *
+ image_->imageInfo().bytesPerPixel()
+ : 0;
+ };
+
private:
sk_sp<SkImage> image_;
SkRect logical_rect_;
@@ -54,6 +54,50 @@
size_t access_threshold = 3,
size_t picture_cache_limit_per_frame = kDefaultPictureCacheLimitPerFrame);
+ virtual ~RasterCache() = default;
+
+ /**
+ * @brief Rasterize a picture object and produce a RasterCacheResult
+ * to be stored in the cache.
+ *
+ * @param picture the SkPicture object to be cached.
+ * @param context the GrContext used for rendering.
+ * @param ctm the transformation matrix used for rendering.
+ * @param dst_color_space the destination color space that the cached
+ * rendering will be drawn into
+ * @param checkerboard a flag indicating whether or not a checkerboard
+ * pattern should be rendered into the cached image for debug
+ * analysis
+ * @return a RasterCacheResult that can draw the rendered picture into
+ * the destination using a simple image blit
+ */
+ virtual std::unique_ptr<RasterCacheResult> RasterizePicture(
+ SkPicture* picture,
+ GrContext* context,
+ const SkMatrix& ctm,
+ SkColorSpace* dst_color_space,
+ bool checkerboard) const;
+
+ /**
+ * @brief Rasterize an engine Layer and produce a RasterCacheResult
+ * to be stored in the cache.
+ *
+ * @param context the PrerollContext containing important information
+ * needed for rendering a layer.
+ * @param layer the Layer object to be cached.
+ * @param ctm the transformation matrix used for rendering.
+ * @param checkerboard a flag indicating whether or not a checkerboard
+ * pattern should be rendered into the cached image for debug
+ * analysis
+ * @return a RasterCacheResult that can draw the rendered layer into
+ * the destination using a simple image blit
+ */
+ virtual std::unique_ptr<RasterCacheResult> RasterizeLayer(
+ PrerollContext* context,
+ Layer* layer,
+ const SkMatrix& ctm,
+ bool checkerboard) const;
+
static SkIRect GetDeviceBounds(const SkRect& rect, const SkMatrix& ctm) {
SkRect device_rect;
ctm.mapRect(&device_rect, rect);
@@ -123,11 +167,15 @@
size_t GetCachedEntriesCount() const;
+ size_t GetLayerCachedEntriesCount() const;
+
+ size_t GetPictureCachedEntriesCount() const;
+
private:
struct Entry {
bool used_this_frame = false;
size_t access_count = 0;
- RasterCacheResult image;
+ std::unique_ptr<RasterCacheResult> image;
};
template <class Cache>
diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h
index 593dec1..a81bf3c 100644
--- a/flow/testing/layer_test.h
+++ b/flow/testing/layer_test.h
@@ -13,6 +13,7 @@
#include "flutter/fml/macros.h"
#include "flutter/testing/canvas_test.h"
#include "flutter/testing/mock_canvas.h"
+#include "flutter/testing/mock_raster_cache.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/utils/SkNWayCanvas.h"
@@ -24,6 +25,13 @@
// |Layer|'s.
// |LayerTest| is a default implementation based on |::testing::Test|.
//
+// By default the preroll and paint contexts will not use a raster cache.
+// If a test needs to verify the proper operation of a layer in the presence
+// of a raster cache then a number of options can be enabled by using the
+// methods |LayerTestBase::use_null_raster_cache()|,
+// |LayerTestBase::use_mock_raster_cache()| or
+// |LayerTestBase::use_skia_raster_cache()|
+//
// |BaseT| should be the base test type, such as |::testing::Test| below.
template <typename BaseT>
class LayerTestBase : public CanvasTestBase<BaseT> {
@@ -55,18 +63,77 @@
false, /* checkerboard_offscreen_layers */
100.0f, /* frame_physical_depth */
1.0f, /* frame_device_pixel_ratio */
- }) {}
+ }) {
+ use_null_raster_cache();
+ }
+
+ /**
+ * @brief Use no raster cache in the preroll_context() and
+ * paint_context() structures.
+ *
+ * This method must be called before using the preroll_context() and
+ * paint_context() structures in calls to the Layer::Preroll() and
+ * Layer::Paint() methods. This is the default mode of operation.
+ *
+ * @see use_mock_raster_cache()
+ * @see use_skia_raster_cache()
+ */
+ void use_null_raster_cache() { set_raster_cache_(nullptr); }
+
+ /**
+ * @brief Use a mock raster cache in the preroll_context() and
+ * paint_context() structures.
+ *
+ * This method must be called before using the preroll_context() and
+ * paint_context() structures in calls to the Layer::Preroll() and
+ * Layer::Paint() methods. The mock raster cache behaves like a normal
+ * raster cache with respect to decisions about when layers and pictures
+ * should be cached, but it does not incur the overhead of rendering the
+ * layers or caching the resulting pixels.
+ *
+ * @see use_null_raster_cache()
+ * @see use_skia_raster_cache()
+ */
+ void use_mock_raster_cache() {
+ set_raster_cache_(std::make_unique<MockRasterCache>());
+ }
+
+ /**
+ * @brief Use a normal raster cache in the preroll_context() and
+ * paint_context() structures.
+ *
+ * This method must be called before using the preroll_context() and
+ * paint_context() structures in calls to the Layer::Preroll() and
+ * Layer::Paint() methods. The Skia raster cache will behave identically
+ * to the raster cache typically used when handling a frame on a device
+ * including rendering the contents of pictures and layers to an
+ * SkImage, but using a software rather than a hardware renderer.
+ *
+ * @see use_null_raster_cache()
+ * @see use_mock_raster_cache()
+ */
+ void use_skia_raster_cache() {
+ set_raster_cache_(std::make_unique<RasterCache>());
+ }
TextureRegistry& texture_regitry() { return texture_registry_; }
+ RasterCache* raster_cache() { return raster_cache_.get(); }
PrerollContext* preroll_context() { return &preroll_context_; }
Layer::PaintContext& paint_context() { return paint_context_; }
private:
+ void set_raster_cache_(std::unique_ptr<RasterCache> raster_cache) {
+ raster_cache_ = std::move(raster_cache);
+ preroll_context_.raster_cache = raster_cache_.get();
+ paint_context_.raster_cache = raster_cache_.get();
+ }
+
Stopwatch raster_time_;
Stopwatch ui_time_;
MutatorsStack mutators_stack_;
TextureRegistry texture_registry_;
+ std::unique_ptr<RasterCache> raster_cache_;
PrerollContext preroll_context_;
Layer::PaintContext paint_context_;
diff --git a/testing/BUILD.gn b/testing/BUILD.gn
index b8c3bd0..69f7d32 100644
--- a/testing/BUILD.gn
+++ b/testing/BUILD.gn
@@ -71,10 +71,13 @@
"canvas_test.h",
"mock_canvas.cc",
"mock_canvas.h",
+ "mock_raster_cache.cc",
+ "mock_raster_cache.h",
]
public_deps = [
":testing_lib",
+ "//flutter/flow",
"//third_party/skia",
]
}
diff --git a/testing/mock_raster_cache.cc b/testing/mock_raster_cache.cc
new file mode 100644
index 0000000..7b178ff
--- /dev/null
+++ b/testing/mock_raster_cache.cc
@@ -0,0 +1,39 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/testing/mock_raster_cache.h"
+#include "flutter/flow/layers/layer.h"
+
+namespace flutter {
+namespace testing {
+
+MockRasterCacheResult::MockRasterCacheResult(SkIRect device_rect)
+ : RasterCacheResult(nullptr, SkRect::MakeEmpty()),
+ device_rect_(device_rect) {}
+
+std::unique_ptr<RasterCacheResult> MockRasterCache::RasterizePicture(
+ SkPicture* picture,
+ GrContext* context,
+ const SkMatrix& ctm,
+ SkColorSpace* dst_color_space,
+ bool checkerboard) const {
+ SkRect logical_rect = picture->cullRect();
+ SkIRect cache_rect = RasterCache::GetDeviceBounds(logical_rect, ctm);
+
+ return std::make_unique<MockRasterCacheResult>(cache_rect);
+}
+
+std::unique_ptr<RasterCacheResult> MockRasterCache::RasterizeLayer(
+ PrerollContext* context,
+ Layer* layer,
+ const SkMatrix& ctm,
+ bool checkerboard) const {
+ SkRect logical_rect = layer->paint_bounds();
+ SkIRect cache_rect = RasterCache::GetDeviceBounds(logical_rect, ctm);
+
+ return std::make_unique<MockRasterCacheResult>(cache_rect);
+}
+
+} // namespace testing
+} // namespace flutter
diff --git a/testing/mock_raster_cache.h b/testing/mock_raster_cache.h
new file mode 100644
index 0000000..0f85118
--- /dev/null
+++ b/testing/mock_raster_cache.h
@@ -0,0 +1,64 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_MOCK_RASTER_CACHE_H_
+#define TESTING_MOCK_RASTER_CACHE_H_
+
+#include "flutter/flow/raster_cache.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkPicture.h"
+
+namespace flutter {
+namespace testing {
+
+/**
+ * @brief A RasterCacheResult implementation that represents a cached Layer or
+ * SkPicture without the overhead of storage.
+ *
+ * This implementation is used by MockRasterCache only for testing proper usage
+ * of the RasterCache in layer unit tests.
+ */
+class MockRasterCacheResult : public RasterCacheResult {
+ public:
+ MockRasterCacheResult(SkIRect device_rect);
+
+ void draw(SkCanvas& canvas, const SkPaint* paint = nullptr) const override{};
+
+ SkISize image_dimensions() const override { return device_rect_.size(); };
+
+ int64_t image_bytes() const override {
+ return image_dimensions().area() *
+ SkColorTypeBytesPerPixel(kBGRA_8888_SkColorType);
+ }
+
+ private:
+ SkIRect device_rect_;
+};
+
+/**
+ * @brief A RasterCache implementation that simulates the act of rendering a
+ * Layer or SkPicture without the overhead of rasterization or pixel storage.
+ * This implementation is used only for testing proper usage of the RasterCache
+ * in layer unit tests.
+ */
+class MockRasterCache : public RasterCache {
+ public:
+ std::unique_ptr<RasterCacheResult> RasterizePicture(
+ SkPicture* picture,
+ GrContext* context,
+ const SkMatrix& ctm,
+ SkColorSpace* dst_color_space,
+ bool checkerboard) const override;
+
+ std::unique_ptr<RasterCacheResult> RasterizeLayer(
+ PrerollContext* context,
+ Layer* layer,
+ const SkMatrix& ctm,
+ bool checkerboard) const override;
+};
+
+} // namespace testing
+} // namespace flutter
+
+#endif // TESTING_MOCK_RASTER_CACHE_H_