Conditionally use offscreen root surface only when needed

Currently helps primarily on iOS when no BackdropFilter is present by lowering energy usage
This commit is contained in:
Jim Graham 2019-12-11 15:10:55 -08:00 committed by GitHub
parent 80d80ff6e6
commit 85953615eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 536 additions and 72 deletions

View File

@ -36,10 +36,11 @@ std::unique_ptr<CompositorContext::ScopedFrame> CompositorContext::AcquireFrame(
ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger) {
return std::make_unique<ScopedFrame>(
*this, gr_context, canvas, view_embedder, root_surface_transformation,
instrumentation_enabled, gpu_thread_merger);
instrumentation_enabled, surface_supports_readback, gpu_thread_merger);
}
CompositorContext::ScopedFrame::ScopedFrame(
@ -49,6 +50,7 @@ CompositorContext::ScopedFrame::ScopedFrame(
ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger)
: context_(context),
gr_context_(gr_context),
@ -56,6 +58,7 @@ CompositorContext::ScopedFrame::ScopedFrame(
view_embedder_(view_embedder),
root_surface_transformation_(root_surface_transformation),
instrumentation_enabled_(instrumentation_enabled),
surface_supports_readback_(surface_supports_readback),
gpu_thread_merger_(gpu_thread_merger) {
context_.BeginFrame(*this, instrumentation_enabled_);
}
@ -67,7 +70,8 @@ CompositorContext::ScopedFrame::~ScopedFrame() {
RasterStatus CompositorContext::ScopedFrame::Raster(
flutter::LayerTree& layer_tree,
bool ignore_raster_cache) {
layer_tree.Preroll(*this, ignore_raster_cache);
bool root_needs_readback = layer_tree.Preroll(*this, ignore_raster_cache);
bool needs_save_layer = root_needs_readback && !surface_supports_readback();
PostPrerollResult post_preroll_result = PostPrerollResult::kSuccess;
if (view_embedder_ && gpu_thread_merger_) {
post_preroll_result = view_embedder_->PostPrerollAction(gpu_thread_merger_);
@ -79,9 +83,19 @@ RasterStatus CompositorContext::ScopedFrame::Raster(
// Clearing canvas after preroll reduces one render target switch when preroll
// paints some raster cache.
if (canvas()) {
if (needs_save_layer) {
FML_LOG(INFO) << "Using SaveLayer to protect non-readback surface";
SkRect bounds = SkRect::Make(layer_tree.frame_size());
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
canvas()->saveLayer(&bounds, &paint);
}
canvas()->clear(SK_ColorTRANSPARENT);
}
layer_tree.Paint(*this, ignore_raster_cache);
if (canvas() && needs_save_layer) {
canvas()->restore();
}
return RasterStatus::kSuccess;
}

View File

@ -45,6 +45,7 @@ class CompositorContext {
ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger);
virtual ~ScopedFrame();
@ -59,6 +60,8 @@ class CompositorContext {
return root_surface_transformation_;
}
bool surface_supports_readback() { return surface_supports_readback_; }
GrContext* gr_context() const { return gr_context_; }
virtual RasterStatus Raster(LayerTree& layer_tree,
@ -71,6 +74,7 @@ class CompositorContext {
ExternalViewEmbedder* view_embedder_;
const SkMatrix& root_surface_transformation_;
const bool instrumentation_enabled_;
const bool surface_supports_readback_;
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger_;
FML_DISALLOW_COPY_AND_ASSIGN(ScopedFrame);
@ -86,6 +90,7 @@ class CompositorContext {
ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger);
void OnGrContextCreated();

View File

@ -9,6 +9,13 @@ namespace flutter {
BackdropFilterLayer::BackdropFilterLayer(sk_sp<SkImageFilter> filter)
: filter_(std::move(filter)) {}
void BackdropFilterLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, true, bool(filter_));
ContainerLayer::Preroll(context, matrix);
}
void BackdropFilterLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "BackdropFilterLayer::Paint");
FML_DCHECK(needs_painting());

View File

@ -15,6 +15,8 @@ class BackdropFilterLayer : public ContainerLayer {
public:
BackdropFilterLayer(sk_sp<SkImageFilter> filter);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:

View File

@ -182,5 +182,36 @@ TEST_F(BackdropFilterLayerTest, Nested) {
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
}
TEST_F(BackdropFilterLayerTest, Readback) {
sk_sp<SkImageFilter> no_filter;
auto layer_filter = SkImageFilters::Paint(SkPaint(SkColors::kMagenta));
auto initial_transform = SkMatrix();
// BDF with filter always reads from surface
auto layer1 = std::make_shared<BackdropFilterLayer>(layer_filter);
preroll_context()->surface_needs_readback = false;
layer1->Preroll(preroll_context(), initial_transform);
EXPECT_TRUE(preroll_context()->surface_needs_readback);
// BDF with no filter does not read from surface itself
auto layer2 = std::make_shared<BackdropFilterLayer>(no_filter);
preroll_context()->surface_needs_readback = false;
layer2->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
// BDF with no filter does not block prior readback value
preroll_context()->surface_needs_readback = true;
layer2->Preroll(preroll_context(), initial_transform);
EXPECT_TRUE(preroll_context()->surface_needs_readback);
// BDF with no filter blocks child with readback
auto mock_layer =
std::make_shared<MockLayer>(SkPath(), SkPaint(), false, false, true);
layer2->Add(mock_layer);
preroll_context()->surface_needs_readback = false;
layer2->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
}
} // namespace testing
} // namespace flutter

View File

@ -22,6 +22,8 @@ void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
SkRect clip_path_bounds = clip_path_.getBounds();
children_inside_clip_ = context->cull_rect.intersect(clip_path_bounds);
if (children_inside_clip_) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipPath(clip_path_);
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
@ -57,11 +59,11 @@ void ClipPathLayer::Paint(PaintContext& context) const {
context.internal_nodes_canvas->clipPath(clip_path_,
clip_behavior_ != Clip::hardEdge);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
if (UsesSaveLayer()) {
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
}
PaintChildren(context);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
}
}

View File

@ -17,6 +17,10 @@ class ClipPathLayer : public ContainerLayer {
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
#if defined(OS_FUCHSIA)
void UpdateScene(SceneUpdateContext& context) override;
#endif // defined(OS_FUCHSIA)

View File

@ -192,5 +192,65 @@ TEST_F(ClipPathLayerTest, PartiallyContainedChild) {
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
}
static bool ReadbackResult(PrerollContext* context,
Clip clip_behavior,
std::shared_ptr<Layer> child,
bool before) {
const SkMatrix initial_matrix = SkMatrix();
const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const SkPath layer_path = SkPath().addRect(layer_bounds);
auto layer = std::make_shared<ClipPathLayer>(layer_path, clip_behavior);
if (child != nullptr) {
layer->Add(child);
}
context->surface_needs_readback = before;
layer->Preroll(context, initial_matrix);
return context->surface_needs_readback;
}
TEST_F(ClipPathLayerTest, Readback) {
PrerollContext* context = preroll_context();
SkPath path;
SkPaint paint;
const Clip hard = Clip::hardEdge;
const Clip soft = Clip::antiAlias;
const Clip save_layer = Clip::antiAliasWithSaveLayer;
std::shared_ptr<MockLayer> nochild;
auto reader = std::make_shared<MockLayer>(path, paint, false, false, true);
auto nonreader = std::make_shared<MockLayer>(path, paint);
// No children, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
// No children, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
// Non readback child, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
// Non readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
// Readback child, no prior readback -> readback after unless SaveLayer
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
// Readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
}
} // namespace testing
} // namespace flutter

View File

@ -15,6 +15,8 @@ void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
SkRect previous_cull_rect = context->cull_rect;
children_inside_clip_ = context->cull_rect.intersect(clip_rect_);
if (children_inside_clip_) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipRect(clip_rect_);
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
@ -50,11 +52,11 @@ void ClipRectLayer::Paint(PaintContext& context) const {
context.internal_nodes_canvas->clipRect(clip_rect_,
clip_behavior_ != Clip::hardEdge);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
if (UsesSaveLayer()) {
context.internal_nodes_canvas->saveLayer(clip_rect_, nullptr);
}
PaintChildren(context);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
}
}

View File

@ -16,6 +16,10 @@ class ClipRectLayer : public ContainerLayer {
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
#if defined(OS_FUCHSIA)
void UpdateScene(SceneUpdateContext& context) override;
#endif // defined(OS_FUCHSIA)

View File

@ -190,5 +190,64 @@ TEST_F(ClipRectLayerTest, PartiallyContainedChild) {
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
}
static bool ReadbackResult(PrerollContext* context,
Clip clip_behavior,
std::shared_ptr<Layer> child,
bool before) {
const SkMatrix initial_matrix = SkMatrix();
const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
auto layer = std::make_shared<ClipRectLayer>(layer_bounds, clip_behavior);
if (child != nullptr) {
layer->Add(child);
}
context->surface_needs_readback = before;
layer->Preroll(context, initial_matrix);
return context->surface_needs_readback;
}
TEST_F(ClipRectLayerTest, Readback) {
PrerollContext* context = preroll_context();
SkPath path;
SkPaint paint;
const Clip hard = Clip::hardEdge;
const Clip soft = Clip::antiAlias;
const Clip save_layer = Clip::antiAliasWithSaveLayer;
std::shared_ptr<MockLayer> nochild;
auto reader = std::make_shared<MockLayer>(path, paint, false, false, true);
auto nonreader = std::make_shared<MockLayer>(path, paint);
// No children, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
// No children, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
// Non readback child, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
// Non readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
// Readback child, no prior readback -> readback after unless SaveLayer
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
// Readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
}
} // namespace testing
} // namespace flutter

View File

@ -16,6 +16,8 @@ void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
SkRect clip_rrect_bounds = clip_rrect_.getBounds();
children_inside_clip_ = context->cull_rect.intersect(clip_rrect_bounds);
if (children_inside_clip_) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipRRect(clip_rrect_);
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
@ -51,11 +53,11 @@ void ClipRRectLayer::Paint(PaintContext& context) const {
context.internal_nodes_canvas->clipRRect(clip_rrect_,
clip_behavior_ != Clip::hardEdge);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
if (UsesSaveLayer()) {
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
}
PaintChildren(context);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
}
}

View File

@ -17,6 +17,10 @@ class ClipRRectLayer : public ContainerLayer {
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
#if defined(OS_FUCHSIA)
void UpdateScene(SceneUpdateContext& context) override;
#endif // defined(OS_FUCHSIA)

View File

@ -195,5 +195,65 @@ TEST_F(ClipRRectLayerTest, PartiallyContainedChild) {
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
}
static bool ReadbackResult(PrerollContext* context,
Clip clip_behavior,
std::shared_ptr<Layer> child,
bool before) {
const SkMatrix initial_matrix = SkMatrix();
const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds);
auto layer = std::make_shared<ClipRRectLayer>(layer_rrect, clip_behavior);
if (child != nullptr) {
layer->Add(child);
}
context->surface_needs_readback = before;
layer->Preroll(context, initial_matrix);
return context->surface_needs_readback;
}
TEST_F(ClipRRectLayerTest, Readback) {
PrerollContext* context = preroll_context();
SkPath path;
SkPaint paint;
const Clip hard = Clip::hardEdge;
const Clip soft = Clip::antiAlias;
const Clip save_layer = Clip::antiAliasWithSaveLayer;
std::shared_ptr<MockLayer> nochild;
auto reader = std::make_shared<MockLayer>(path, paint, false, false, true);
auto nonreader = std::make_shared<MockLayer>(path, paint);
// No children, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
// No children, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
// Non readback child, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
// Non readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
// Readback child, no prior readback -> readback after unless SaveLayer
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
// Readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
}
} // namespace testing
} // namespace flutter

View File

@ -9,6 +9,13 @@ namespace flutter {
ColorFilterLayer::ColorFilterLayer(sk_sp<SkColorFilter> filter)
: filter_(std::move(filter)) {}
void ColorFilterLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
ContainerLayer::Preroll(context, matrix);
}
void ColorFilterLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "ColorFilterLayer::Paint");
FML_DCHECK(needs_painting());

View File

@ -15,6 +15,8 @@ class ColorFilterLayer : public ContainerLayer {
public:
ColorFilterLayer(sk_sp<SkColorFilter> filter);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:

View File

@ -195,5 +195,24 @@ TEST_F(ColorFilterLayerTest, Nested) {
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
}
TEST_F(ColorFilterLayerTest, Readback) {
auto layer_filter = SkColorFilters::LinearToSRGBGamma();
auto initial_transform = SkMatrix();
// ColorFilterLayer does not read from surface
auto layer = std::make_shared<ColorFilterLayer>(layer_filter);
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
// ColorFilterLayer blocks child with readback
auto mock_layer =
std::make_shared<MockLayer>(SkPath(), SkPaint(), false, false, true);
layer->Add(mock_layer);
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
}
} // namespace testing
} // namespace flutter

View File

@ -27,6 +27,34 @@ uint64_t Layer::NextUniqueID() {
void Layer::Preroll(PrerollContext* context, const SkMatrix& matrix) {}
Layer::AutoPrerollSaveLayerState::AutoPrerollSaveLayerState(
PrerollContext* preroll_context,
bool save_layer_is_active,
bool layer_itself_performs_readback)
: preroll_context_(preroll_context),
save_layer_is_active_(save_layer_is_active),
layer_itself_performs_readback_(layer_itself_performs_readback) {
if (save_layer_is_active_) {
prev_surface_needs_readback_ = preroll_context_->surface_needs_readback;
preroll_context_->surface_needs_readback = false;
}
}
Layer::AutoPrerollSaveLayerState Layer::AutoPrerollSaveLayerState::Create(
PrerollContext* preroll_context,
bool save_layer_is_active,
bool layer_itself_performs_readback) {
return Layer::AutoPrerollSaveLayerState(preroll_context, save_layer_is_active,
layer_itself_performs_readback);
}
Layer::AutoPrerollSaveLayerState::~AutoPrerollSaveLayerState() {
if (save_layer_is_active_) {
preroll_context_->surface_needs_readback =
(prev_surface_needs_readback_ || layer_itself_performs_readback_);
}
}
#if defined(OS_FUCHSIA)
void Layer::UpdateScene(SceneUpdateContext& context) {}
#endif // defined(OS_FUCHSIA)

View File

@ -49,6 +49,7 @@ struct PrerollContext {
MutatorsStack& mutators_stack;
SkColorSpace* dst_color_space;
SkRect cull_rect;
bool surface_needs_readback;
// These allow us to paint in the end of subtree Preroll.
const Stopwatch& raster_time;
@ -76,6 +77,32 @@ class Layer {
virtual void Preroll(PrerollContext* context, const SkMatrix& matrix);
// Used during Preroll by layers that employ a saveLayer to manage the
// PrerollContext settings with values affected by the saveLayer mechanism.
// This object must be created before calling Preroll on the children to
// set up the state for the children and then restore the state upon
// destruction.
class AutoPrerollSaveLayerState {
public:
FML_WARN_UNUSED_RESULT static AutoPrerollSaveLayerState Create(
PrerollContext* preroll_context,
bool save_layer_is_active = true,
bool layer_itself_performs_readback = false);
~AutoPrerollSaveLayerState();
private:
AutoPrerollSaveLayerState(PrerollContext* preroll_context,
bool save_layer_is_active,
bool layer_itself_performs_readback);
PrerollContext* preroll_context_;
bool save_layer_is_active_;
bool layer_itself_performs_readback_;
bool prev_surface_needs_readback_;
};
struct PaintContext {
// When splitting the scene into multiple canvases (e.g when embedding
// a platform view on iOS) during the paint traversal we apply the non leaf

View File

@ -26,13 +26,13 @@ void LayerTree::RecordBuildTime(fml::TimePoint start) {
build_finish_ = fml::TimePoint::Now();
}
void LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
bool ignore_raster_cache) {
TRACE_EVENT0("flutter", "LayerTree::Preroll");
if (!root_layer_) {
FML_LOG(ERROR) << "The scene did not specify any layers.";
return;
return false;
}
SkColorSpace* color_space =
@ -47,6 +47,7 @@ void LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
stack,
color_space,
kGiantRect,
false,
frame.context().raster_time(),
frame.context().ui_time(),
frame.context().texture_registry(),
@ -55,6 +56,7 @@ void LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
frame_device_pixel_ratio_};
root_layer_->Preroll(&context, frame.root_surface_transformation());
return context.surface_needs_readback;
}
#if defined(OS_FUCHSIA)
@ -152,6 +154,7 @@ sk_sp<SkPicture> LayerTree::Flatten(const SkRect& bounds) {
unused_stack, // mutator stack
nullptr, // SkColorSpace* dst_color_space
kGiantRect, // SkRect cull_rect
false, // layer reads from surface
unused_stopwatch, // frame time (dont care)
unused_stopwatch, // engine time (dont care)
unused_texture_registry, // texture registry (not supported)

View File

@ -23,7 +23,14 @@ class LayerTree {
float frame_physical_depth,
float frame_device_pixel_ratio);
void Preroll(CompositorContext::ScopedFrame& frame,
// Perform a preroll pass on the tree and return information about
// the tree that affects rendering this frame.
//
// Returns:
// - a boolean indicating whether or not the top level of the
// layer tree performs any operations that require readback
// from the root surface.
bool Preroll(CompositorContext::ScopedFrame& frame,
bool ignore_raster_cache = false);
#if defined(OS_FUCHSIA)

View File

@ -26,6 +26,7 @@ class LayerTreeTest : public CanvasTest {
nullptr,
root_transform_,
false,
true,
nullptr)) {}
LayerTree& layer_tree() { return layer_tree_; }

View File

@ -58,6 +58,8 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
child_matrix.postTranslate(offset_.fX, offset_.fY);
context->mutators_stack.PushTransform(
SkMatrix::MakeTrans(offset_.fX, offset_.fY));
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
OpacityLayerBase::Preroll(context, child_matrix);
context->mutators_stack.Pop();

View File

@ -308,5 +308,24 @@ TEST_F(OpacityLayerTest, Nested) {
EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls);
}
TEST_F(OpacityLayerTest, Readback) {
auto initial_transform = SkMatrix();
auto layer = std::make_shared<OpacityLayer>(kOpaque_SkAlphaType, SkPoint());
layer->Add(std::make_shared<MockLayer>(SkPath()));
// OpacityLayer does not read from surface
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
// OpacityLayer blocks child with readback
auto mock_layer =
std::make_shared<MockLayer>(SkPath(), SkPaint(), false, false, true);
layer->Add(mock_layer);
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
}
} // namespace testing
} // namespace flutter

View File

@ -49,6 +49,8 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
TRACE_EVENT0("flutter", "PhysicalShapeLayer::Preroll");
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
PhysicalShapeLayerBase::Preroll(context, matrix);
if (elevation() == 0) {
@ -100,7 +102,7 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const {
break;
}
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
if (UsesSaveLayer()) {
// If we want to avoid the bleeding edge artifact
// (https://github.com/flutter/flutter/issues/18057#issue-328003931)
// using saveLayer, we have to call drawPaint instead of drawPath as

View File

@ -46,6 +46,10 @@ class PhysicalShapeLayer : public PhysicalShapeLayerBase {
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
private:
SkColor shadow_color_;
SkPath path_;

View File

@ -202,5 +202,68 @@ TEST_F(PhysicalShapeLayerTest, ElevationComplex) {
0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));
}
static bool ReadbackResult(PrerollContext* context,
Clip clip_behavior,
std::shared_ptr<Layer> child,
bool before) {
const SkMatrix initial_matrix = SkMatrix();
const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const SkPath layer_path = SkPath().addRect(layer_bounds);
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, clip_behavior);
if (child != nullptr) {
layer->Add(child);
}
context->surface_needs_readback = before;
layer->Preroll(context, initial_matrix);
return context->surface_needs_readback;
}
TEST_F(PhysicalShapeLayerTest, Readback) {
PrerollContext* context = preroll_context();
SkPath path;
SkPaint paint;
const Clip hard = Clip::hardEdge;
const Clip soft = Clip::antiAlias;
const Clip save_layer = Clip::antiAliasWithSaveLayer;
std::shared_ptr<MockLayer> nochild;
auto reader = std::make_shared<MockLayer>(path, paint, false, false, true);
auto nonreader = std::make_shared<MockLayer>(path, paint);
// No children, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
// No children, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
// Non readback child, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
// Non readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
// Readback child, no prior readback -> readback after unless SaveLayer
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
// Readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
}
} // namespace testing
} // namespace flutter

View File

@ -11,6 +11,12 @@ ShaderMaskLayer::ShaderMaskLayer(sk_sp<SkShader> shader,
SkBlendMode blend_mode)
: shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {}
void ShaderMaskLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
ContainerLayer::Preroll(context, matrix);
}
void ShaderMaskLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "ShaderMaskLayer::Paint");
FML_DCHECK(needs_painting());

View File

@ -17,6 +17,8 @@ class ShaderMaskLayer : public ContainerLayer {
const SkRect& mask_rect,
SkBlendMode blend_mode);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:

View File

@ -253,5 +253,27 @@ TEST_F(ShaderMaskLayerTest, Nested) {
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
}
TEST_F(ShaderMaskLayerTest, Readback) {
auto initial_transform = SkMatrix();
const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f);
auto layer_filter =
SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f);
auto layer = std::make_shared<ShaderMaskLayer>(layer_filter, layer_bounds,
SkBlendMode::kSrc);
// ShaderMaskLayer does not read from surface
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
// ShaderMaskLayer blocks child with readback
auto mock_layer =
std::make_shared<MockLayer>(SkPath(), SkPaint(), false, false, true);
layer->Add(mock_layer);
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
}
} // namespace testing
} // namespace flutter

View File

@ -37,6 +37,7 @@ class LayerTestBase : public CanvasTestBase<BaseT> {
nullptr, /* external_view_embedder */
mutators_stack_, TestT::mock_canvas().imageInfo().colorSpace(),
kGiantRect, /* cull_rect */
false, /* layer reads from surface */
raster_time_, ui_time_, texture_registry_,
false, /* checkerboard_offscreen_layers */
100.0f, /* frame_physical_depth */

View File

@ -10,11 +10,13 @@ namespace testing {
MockLayer::MockLayer(SkPath path,
SkPaint paint,
bool fake_has_platform_view,
bool fake_needs_system_composite)
bool fake_needs_system_composite,
bool fake_reads_surface)
: fake_paint_path_(path),
fake_paint_(paint),
fake_has_platform_view_(fake_has_platform_view),
fake_needs_system_composite_(fake_needs_system_composite) {}
fake_needs_system_composite_(fake_needs_system_composite),
fake_reads_surface_(fake_reads_surface) {}
void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
parent_mutators_ = context->mutators_stack;
@ -26,6 +28,9 @@ void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
context->has_platform_view = fake_has_platform_view_;
set_paint_bounds(fake_paint_path_.getBounds());
set_needs_system_composite(fake_needs_system_composite_);
if (fake_reads_surface_) {
context->surface_needs_readback = true;
}
}
void MockLayer::Paint(PaintContext& context) const {

View File

@ -19,7 +19,8 @@ class MockLayer : public Layer {
MockLayer(SkPath path,
SkPaint paint = SkPaint(),
bool fake_has_platform_view = false,
bool fake_needs_system_composite = false);
bool fake_needs_system_composite = false,
bool fake_reads_surface = false);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
@ -40,6 +41,7 @@ class MockLayer : public Layer {
bool parent_has_platform_view_ = false;
bool fake_has_platform_view_ = false;
bool fake_needs_system_composite_ = false;
bool fake_reads_surface_ = false;
FML_DISALLOW_COPY_AND_ASSIGN(MockLayer);
};

View File

@ -333,6 +333,7 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
external_view_embedder, // external view embedder
root_surface_transformation, // root surface transformation
true, // instrumentation enabled
frame->supports_readback(), // surface supports pixel reads
gpu_thread_merger_ // thread merger
);
@ -377,7 +378,7 @@ static sk_sp<SkData> ScreenshotLayerTreeAsPicture(
// https://github.com/flutter/flutter/issues/23435
auto frame = compositor_context.AcquireFrame(
nullptr, recorder.getRecordingCanvas(), nullptr,
root_surface_transformation, false, nullptr);
root_surface_transformation, false, true, nullptr);
frame->Raster(*tree, true);
@ -434,7 +435,7 @@ static sk_sp<SkData> ScreenshotLayerTreeAsImage(
// surface if we don't call the base method.
auto frame = compositor_context.flutter::CompositorContext::AcquireFrame(
surface_context, canvas, nullptr, root_surface_transformation, false,
nullptr);
true, nullptr);
canvas->clear(SK_ColorTRANSPARENT);
frame->Raster(*tree, true);
canvas->flush();

View File

@ -10,8 +10,12 @@
namespace flutter {
SurfaceFrame::SurfaceFrame(sk_sp<SkSurface> surface,
bool supports_readback,
const SubmitCallback& submit_callback)
: submitted_(false), surface_(surface), submit_callback_(submit_callback) {
: submitted_(false),
surface_(surface),
supports_readback_(supports_readback),
submit_callback_(submit_callback) {
FML_DCHECK(submit_callback_);
}

View File

@ -21,7 +21,9 @@ class SurfaceFrame {
using SubmitCallback =
std::function<bool(const SurfaceFrame& surface_frame, SkCanvas* canvas)>;
SurfaceFrame(sk_sp<SkSurface> surface, const SubmitCallback& submit_callback);
SurfaceFrame(sk_sp<SkSurface> surface,
bool supports_readback,
const SubmitCallback& submit_callback);
~SurfaceFrame();
@ -31,9 +33,12 @@ class SurfaceFrame {
sk_sp<SkSurface> SkiaSurface() const;
bool supports_readback() { return supports_readback_; }
private:
bool submitted_;
sk_sp<SkSurface> surface_;
bool supports_readback_;
SubmitCallback submit_callback_;
bool PerformSubmit();

View File

@ -180,19 +180,6 @@ static sk_sp<SkSurface> WrapOnscreenSurface(GrContext* context,
);
}
static sk_sp<SkSurface> CreateOffscreenSurface(GrContext* context,
const SkISize& size) {
const SkImageInfo image_info = SkImageInfo::MakeN32(
size.fWidth, size.fHeight, kOpaque_SkAlphaType, SkColorSpace::MakeSRGB());
const SkSurfaceProps surface_props(
SkSurfaceProps::InitType::kLegacyFontHost_InitType);
return SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, image_info, 0,
kBottomLeft_GrSurfaceOrigin,
&surface_props);
}
bool GPUSurfaceGL::CreateOrUpdateSurfaces(const SkISize& size) {
if (onscreen_surface_ != nullptr &&
size == SkISize::Make(onscreen_surface_->width(),
@ -206,14 +193,13 @@ bool GPUSurfaceGL::CreateOrUpdateSurfaces(const SkISize& size) {
// Either way, we need to get rid of previous surface.
onscreen_surface_ = nullptr;
offscreen_surface_ = nullptr;
if (size.isEmpty()) {
FML_LOG(ERROR) << "Cannot create surfaces of empty size.";
return false;
}
sk_sp<SkSurface> onscreen_surface, offscreen_surface;
sk_sp<SkSurface> onscreen_surface;
onscreen_surface =
WrapOnscreenSurface(context_.get(), // GL context
@ -228,16 +214,7 @@ bool GPUSurfaceGL::CreateOrUpdateSurfaces(const SkISize& size) {
return false;
}
if (delegate_->UseOffscreenSurface()) {
offscreen_surface = CreateOffscreenSurface(context_.get(), size);
if (offscreen_surface == nullptr) {
FML_LOG(ERROR) << "Could not create offscreen surface.";
return false;
}
}
onscreen_surface_ = std::move(onscreen_surface);
offscreen_surface_ = std::move(offscreen_surface);
return true;
}
@ -263,7 +240,7 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceGL::AcquireFrame(const SkISize& size) {
// external view embedder may want to render to the root surface.
if (!render_to_surface_) {
return std::make_unique<SurfaceFrame>(
nullptr, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) {
nullptr, true, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) {
return true;
});
}
@ -285,7 +262,8 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceGL::AcquireFrame(const SkISize& size) {
return weak ? weak->PresentSurface(canvas) : false;
};
return std::make_unique<SurfaceFrame>(surface, submit_callback);
return std::make_unique<SurfaceFrame>(
surface, delegate_->SurfaceSupportsReadback(), submit_callback);
}
bool GPUSurfaceGL::PresentSurface(SkCanvas* canvas) {
@ -293,15 +271,6 @@ bool GPUSurfaceGL::PresentSurface(SkCanvas* canvas) {
return false;
}
if (offscreen_surface_ != nullptr) {
TRACE_EVENT0("flutter", "CopyTextureOnscreen");
SkPaint paint;
SkCanvas* onscreen_canvas = onscreen_surface_->getCanvas();
onscreen_canvas->clear(SK_ColorTRANSPARENT);
onscreen_canvas->drawImage(offscreen_surface_->makeImageSnapshot(), 0, 0,
&paint);
}
{
TRACE_EVENT0("flutter", "SkCanvas::Flush");
onscreen_surface_->getCanvas()->flush();
@ -346,7 +315,7 @@ sk_sp<SkSurface> GPUSurfaceGL::AcquireRenderSurface(
return nullptr;
}
return offscreen_surface_ != nullptr ? offscreen_surface_ : onscreen_surface_;
return onscreen_surface_;
}
// |Surface|

View File

@ -50,7 +50,6 @@ class GPUSurfaceGL : public Surface {
GPUSurfaceGLDelegate* delegate_;
sk_sp<GrContext> context_;
sk_sp<SkSurface> onscreen_surface_;
sk_sp<SkSurface> offscreen_surface_;
bool context_owner_;
// TODO(38466): Refactor GPU surface APIs take into account the fact that an
// external view embedder may want to render to the root surface. This is a

View File

@ -12,8 +12,8 @@ bool GPUSurfaceGLDelegate::GLContextFBOResetAfterPresent() const {
return false;
}
bool GPUSurfaceGLDelegate::UseOffscreenSurface() const {
return false;
bool GPUSurfaceGLDelegate::SurfaceSupportsReadback() const {
return true;
}
SkMatrix GPUSurfaceGLDelegate::GLContextSurfaceTransformation() const {

View File

@ -36,8 +36,9 @@ class GPUSurfaceGLDelegate : public GPUSurfaceDelegate {
// subsequent frames.
virtual bool GLContextFBOResetAfterPresent() const;
// Create an offscreen surface to render into before onscreen composition.
virtual bool UseOffscreenSurface() const;
// Indicates whether or not the surface supports pixel readback as used in
// circumstances such as a BackdropFilter.
virtual bool SurfaceSupportsReadback() const;
// A transformation applied to the onscreen surface before the canvas is
// flushed.

View File

@ -152,7 +152,7 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceMetal::AcquireFrame(const SkISize& size)
return true;
};
return std::make_unique<SurfaceFrame>(std::move(surface), submit_callback);
return std::make_unique<SurfaceFrame>(std::move(surface), true, submit_callback);
}
// |Surface|

View File

@ -29,7 +29,7 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceSoftware::AcquireFrame(
// external view embedder may want to render to the root surface.
if (!render_to_surface_) {
return std::make_unique<SurfaceFrame>(
nullptr, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) {
nullptr, true, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) {
return true;
});
}
@ -69,7 +69,7 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceSoftware::AcquireFrame(
return self->delegate_->PresentBackingStore(surface_frame.SkiaSurface());
};
return std::make_unique<SurfaceFrame>(backing_store, on_submit);
return std::make_unique<SurfaceFrame>(backing_store, true, on_submit);
}
// |Surface|

View File

@ -40,7 +40,7 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceVulkan::AcquireFrame(
}
return weak_this->window_.SwapBuffers();
};
return std::make_unique<SurfaceFrame>(std::move(surface),
return std::make_unique<SurfaceFrame>(std::move(surface), true,
std::move(callback));
}

View File

@ -48,7 +48,7 @@ class IOSSurfaceGL final : public IOSSurface,
intptr_t GLContextFBO() const override;
bool UseOffscreenSurface() const override;
bool SurfaceSupportsReadback() const override;
// |GPUSurfaceGLDelegate|
ExternalViewEmbedder* GetExternalViewEmbedder() override;

View File

@ -49,11 +49,13 @@ intptr_t IOSSurfaceGL::GLContextFBO() const {
return IsValid() ? render_target_->framebuffer() : GL_NONE;
}
bool IOSSurfaceGL::UseOffscreenSurface() const {
// The onscreen surface wraps a GL renderbuffer, which is extremely slow to read.
// Certain filter effects require making a copy of the current destination, so we
// always render to an offscreen surface, which will be much quicker to read/copy.
return true;
bool IOSSurfaceGL::SurfaceSupportsReadback() const {
// The onscreen surface wraps a GL renderbuffer, which is extremely slow to read on iOS.
// Certain filter effects, in particular BackdropFilter, require making a copy of
// the current destination. For performance, the iOS surface will specify that it
// does not support readback so that the engine compositor can implement a workaround
// such as rendering the scene to an offscreen surface or Skia saveLayer.
return false;
}
bool IOSSurfaceGL::GLContextMakeCurrent() {

View File

@ -20,6 +20,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame {
nullptr,
root_surface_transformation,
instrumentation_enabled,
true,
nullptr),
session_connection_(session_connection) {}
@ -96,6 +97,7 @@ CompositorContext::AcquireFrame(
flutter::ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger) {
// TODO: The AcquireFrame interface is too broad and must be refactored to get
// rid of the context and canvas arguments as those seem to be only used for

View File

@ -45,6 +45,7 @@ class CompositorContext final : public flutter::CompositorContext {
flutter::ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger) override;
FML_DISALLOW_COPY_AND_ASSIGN(CompositorContext);

View File

@ -26,8 +26,10 @@ bool Surface::IsValid() {
std::unique_ptr<flutter::SurfaceFrame> Surface::AcquireFrame(
const SkISize& size) {
return std::make_unique<flutter::SurfaceFrame>(
nullptr, [](const flutter::SurfaceFrame& surface_frame,
SkCanvas* canvas) { return true; });
nullptr, true,
[](const flutter::SurfaceFrame& surface_frame, SkCanvas* canvas) {
return true;
});
}
// |flutter::Surface|