Hi everyone,
We’re proposing a breaking change to our widgets’ clip behavior so our average frame time could be 30% faster as benchmarked in our device lab.
Previously, Flutter added a saveLayer after each clipPath (unless the path is a simple axis-aligned rectangle) to avoid the bleeding edge artifacts as described in https://github.com/flutter/flutter/issues/18057. Such behaviors were universal to material apps through widgets like PhysicalShape and PhysicalModel which clip their content.
We are proposing to instead never add saveLayer automatically (https://github.com/flutter/engine/pull/5420). Moreover, we're proposing that framework widgets (e.g., PhysicalShape and PhysicalModel) won’t clip their content by default. To clip the content, developers would have to explicitly send in one of the following 3 enums to the widget:
clip:Clip.hardEdge
(fastest clip without anti-aliasing, reasonable fidelity)
clip: Clip.antiAlias
(a little slower, smoother edges, better fidelity)
clip: Clip.antiAliasWithSaveLayer
(very slow, highest fidelity and slightly different semantics)
(by default, clip: Clip.none.)
We’re proposing this change for the following reasons:
saveLayer is very costly in performance, but we don’t see many of those bleeding edge artifacts in real applications. (In fact, in studying this, we discovered that we had a bug that would have caused these artifacts, but despite that, we haven't heard any complaints about them.)
With or without a saveLayer has different semantics so we shouldn’t automatically add a saveLayer. See the following fiddles and comment on issue 18057 for illustrations:
In the end, clipping always has some performance cost so we’d like to provide our developers full capability all the way to disabling clipping for the best performance.
We do not propose to change the default behaviour of Canvas drawing, which currently has anti-aliasing enabled by default. That is: the anti-alias bit in the clip flag only affects whether the clip is anti-aliased or not; it does not affect whether the path inside the widget is anti-aliased or not.
To port the code, you have 4 choices:
Leave your code as is if your content does not need to be clipped (i.e., none of the widgets’ children expand outside their parent’s boundary). This will probably have a positive impact on your overall performance.
Add “clip: Clip.hardEdge” if you need clipping, and clipping without anti-alias is good enough to your (and your clients’) eyes. This will be the common case when you clip rectangles or shapes with very small curved areas (such as the corners of rounded rectangles).
Add “clip.antiAlias” if you need anti-aliased clipping. This will give you smoother edges at a slightly higher cost. This will be the common case when dealing with circles and arcs.
Add “clip.antiAliasWithSaveLayer” if you want the exact same behavior as before. Be aware that it’s very costly in performance. This will be only rarely needed. One case where you might need this is if you have an image overlaid on a very different background color. In these cases, consider whether you can avoid overlaying multiple colors in one spot (e.g. by having the background color only present where the image is absent).
We expect most parts of most applications to be mostly unaffected by this. The most common case where you will be affected by this is where you have Card widgets, Material widgets with rounded corners or other shapes, or Containers with rounded corners or shapes, and inside those widgets you have images or background images that are "full bleed" (rendered right up to the edge). You will likely want to enable clipping (either “clip: Clip.hardEdge” or “clip.antiAlias”) for those containers.
Please let us know what you think about this proposal and whether we should proceed. If we end up applying this change and you need some help porting the code, please contact me (liyu...@google.com) and I’m very happy to help.
Thanks,
Yuqian