New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a ratio cap to decoded animated image frames #6310
Conversation
lib/ui/painting.dart
Outdated
@@ -1567,6 +1567,22 @@ class FrameInfo extends NativeFieldWrapperClass2 { | |||
Image get image native 'FrameInfo_image'; | |||
} | |||
|
|||
/// The default max amount of memory to use for caching decoded animated image | |||
/// frames compared to total undecoded size. See [decodedCacheRatioCapOverride]. | |||
const double kDefaultDecodedCacheRatioCap = 25.0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this private?
lib/ui/painting.dart
Outdated
|
||
/// By default individual frames of animated images are cached into memory to | ||
/// avoid using CPU to re-decode them for every loop in the animation. This | ||
/// behavior will result in out of memory crashes when decoding large |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "out-of-memory"
lib/ui/painting.dart
Outdated
/// By default individual frames of animated images are cached into memory to | ||
/// avoid using CPU to re-decode them for every loop in the animation. This | ||
/// behavior will result in out of memory crashes when decoding large | ||
/// (or large amounts of) animated images. Set this value to limit how much |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
amounts -> numbers
lib/ui/painting.dart
Outdated
/// unessential decoded frames. A setting of `1.0` or less disables all caching | ||
/// of unessential decoded frames. See [kDefaultDecodedCacheRatioCap] for the | ||
/// default value. | ||
double decodedCacheRatioCapOverride; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no setter here, meaning that setting the value has no effect. Document how to make it take effect (e.g. "this value only takes effect when instantiateImageCodec
is invoked; to change the cache on the fly, the image must be redecoded with a new value).
But also, this is a global value with non-immediate side-effects, which is generally frowned upon. I would recommend making this an argument to instantiateImageCodec
rather than a global variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Consider how a framework that wanted to pick the ratio on a per-image basis would have to be written. Or what would happen if two libraries with different opinions were to interact. They'd be clobbering each other's setting of this variable.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On it being a global: making it a param of the constructor forces this to bubble out pretty far in order for users to actually set it when constructing their images, since Codec
isn't being constructed directly and ends up being created from several different top level widgets. From what I can tell AssetImage
, ExtractAssetImage
, FileImage
, NetworkImage
, and MemoryImage
all end up eventually instantiating Codec
and would need the param added to their constructors too in order to pass it through. There also looks like there's static constructors attached to Image
itself for each of those classes: Image.asset
, Image.file
, Image.network
, Image.memory
. I'm good with adding this new param throughout if you think it's preferable to a global setter? When I first wrote this I was thinking it was a bigger problem to have a param existing across all those signatures in order to flip a setting way over in this class, but I hadn't thought about people wanting to potentially set it on a per-image basis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked offline. It's possible to avoid both of these problems if the multiple instantiateImageCodec
calls in image_provider.dart
are diverted into a new singular method in binding.dart
. A global is more of an accepted practice on the framework side, so the default can be defined and overridden there.
Moved the cap into the framework in flutter/flutter#22452. |
lib/ui/painting.dart
Outdated
/// | ||
/// The following image formats are supported: {@macro flutter.dart:ui.imageFormats} | ||
/// [decodedCacheRatioCap] is the default maximum multiple of the compressed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid starting with a lower case letter by using a style like The [foo] parameter is..
lib/ui/painting.dart
Outdated
/// setting this to `2.0` means that a 400KB GIF would be allowed at most to use | ||
/// 800KB of memory caching unessential decoded frames. Caching decoded frames | ||
/// saves CPU but can result in out-of-memory crashes when decoding large (or | ||
/// multiple) animated images. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should add some comment advising about what blow-up factors are common, e.g. mentioning that 2x is pretty much never going to be a reasonable factor.
lib/ui/painting.dart
Outdated
/// | ||
/// The returned future can complete with an error if the image decoding has | ||
/// failed. | ||
Future<Codec> instantiateImageCodec(Uint8List list) { | ||
Future<Codec> instantiateImageCodec(Uint8List list, | ||
{double decodedCacheRatioCap = double.maxFinite}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why maxFinite
instead of infinity
?
Dart code LGTM modulo comments. I'll let Chinmay or Jason review the C++. |
lib/ui/painting/codec.cc
Outdated
SkBitmap& requiredBitmap = frameBitmaps_[requiredFrame]; | ||
// For simplicity, do not try to cache old frames | ||
|
||
SkBitmap requiredBitmap = *frameBitmaps_[requiredFrame]->bitmap_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider extracting a local ptr to frameBitmaps_[requiredFrame]->bitmap_
from the condition in line 408, then using that here.
/// saves CPU but can result in out-of-memory crashes when decoding large (or | ||
/// multiple) animated images. Note that GIFs are highly compressed, and it's | ||
/// unlikely that a factor that low will be sufficient to cache all decoded | ||
/// frames. The default value is `25.0`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: After talking in person, this will be set in a followup PR.
Provide a relative, per-image limit to the amount of memory that's used to cache decoded image frames. Adds an overridable default to `dart:ui` that developers can set to control how much memory images are allowed to use decoded vs undecoded. Note that required frames are always cached regardless of the ratio cap, because they're currently necessary for the GIF to animate. Previously cached unessential frames are not cleared in response to the cache hitting or exceeding the cap. Fixes #20998, #14344
Sets it as an optional param to `instantiateImageCodec`. Will be passed in by the framework in a followup PR.
flutter/engine@bc6df5b...068d6f8 git log bc6df5b..068d6f8 --no-merges --oneline 068d6f8 Roll src/third_party/skia f40ddfabb7f0..a38ab3b094ba (2 commits) (flutter/engine#6484) 7afbcd9 Add a ratio cap to decoded animated image frames (flutter/engine#6310) f8d8777 Roll src/third_party/skia 42137de2b2b8..f40ddfabb7f0 (7 commits) (flutter/engine#6482) The AutoRoll server is located here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+/master/autoroll/README.md If the roll is causing failures, please contact the current sheriff, who should be CC'd on the roll, and stop the roller if necessary.
Users can set `PaintingBinding.decodedCacheRatioCap` to control the max amount of memory used per image to avoid decoding frames each animation loop. This depends on flutter/engine#6310. Fixes #20998, and fixes #14344
Provide a relative, per-image limit to the amount of memory
that's used to cache decoded image frames. Adds an overridable default
to
dart:ui
that developers can set to control how much memory imagesare allowed to use decoded vs undecoded.
Note that required frames are always cached regardless of the ratio cap,
because they're currently necessary for the GIF to animate. Previously
cached unessential frames are not cleared in response to the cache
hitting or exceeding the cap.
See flutter/flutter#20998 and flutter/flutter#14344