-
Notifications
You must be signed in to change notification settings - Fork 6k
Improve DisplayList bounds calculations #28656
Conversation
|
After re-running the internal memory consumption tests on this cherry-picked PR, it appears that this patch results in undoing the regressions of flutter/flutter#88745 |
dnfield
left a comment
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.
Cool! Mostly nits, main concern is around the threading questions.
| static constexpr int kIsNonGeometric = 0x00; | ||
|
|
||
| // A geometric operation that is defined as a fill operation | ||
| // regardless of what the current paint Style is set to. | ||
| // This flag will automatically assume |kApplyMaskFilter|. | ||
| static constexpr int kIsFilledGeometry = 0x01; | ||
|
|
||
| // A geometric operation that is defined as a stroke operation | ||
| // regardless of what the current paint Style is set to. | ||
| // This flag will automatically assume |kApplyMaskFilter|. | ||
| static constexpr int kIsStrokedGeometry = 0x02; | ||
|
|
||
| // A geometric operation that may be a stroke or fill operation | ||
| // depending on the current state of the paint Style attribute. | ||
| // This flag will automatically assume |kApplyMaskFilter|. | ||
| static constexpr int kIsDrawnGeometry = 0x04; | ||
|
|
||
| static constexpr int kIsAnyGeometryMask = // | ||
| kIsFilledGeometry | // | ||
| kIsStrokedGeometry | // | ||
| kIsDrawnGeometry; |
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 an enum class.
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.
(for this and below)
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.
These are mask constants...?
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.
Yes, you can still define them as an enum class to get some more type safety, but I believe you'd have to overload any operators you use to do so. Your call.
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.
I like the idea of using an enum if we don't want to make it an enum class which would need overloading the operators. Just groups the various values better IMO.
enum FlagValue {
kSomething = 1 << 0,
kSomethingElse = 1 << 1,
kAnotherThing = 1 << 2,
};
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.
I see lots of tutorials on this, but none of them answer the basic question of "why".
enums are enumerable. You can see all of its values right there.
Except if it is flags then you don't see all of its values right there. You can make up other values by combining them. Which of the enumerated values is kIsFrog | kIsZebra. I don't see a "zebra frog" in the list and such an animal would not likely survive long.
So, this concept has philosophical problems.
What advantages does it provide? "Hey, it's an enum!" - and how does that help anything?
One of the advantages of enums is knowing that your switch statements are complete, but you don't use flags in switches and if you did this aspect of enums would lie to you. As long as you had a case for each bit flag (which would be a horribly inefficient switch table) then it would think things were complete.
I suppose it has a type associated with it, but I feel like it is expressing "apples" as if they were "oranges".
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.
If you use an enum class the compiler will help make sure you use the right types and don't end up accidentally mixing things up. But it does require operator overloading
|
I filed flutter/flutter#90232 and linked it into flutter/flutter#85737 for updating the DisplayList API names. |
chinmaygarde
left a comment
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.
Thanks for the great PR commit message. Helped me understand what you were going for. I took a close look at the unit-tests but must admit I could not follow how the tolerances were setup. Some other minor comments but otherwise looks good to me.
flow/display_list_utils.h
Outdated
| virtual BoundsAccumulator* accumulatorForRestore() { return outer_; } | ||
| virtual SkRect getLayerBounds() { return SkRect::MakeEmpty(); } | ||
|
|
||
| // is_flooded is to true if we ever encounter an operation |
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.
In this case, won't the bounds be infinitely big? Not sure why we need two fields to convey this information.
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.
Only if they failed to provide a cull rect or surround these operations with a clip.
I'm following what happens with SkPicture. If you record one with no bounds then the cull rect defaults to an empty rect. When they encounter an op with no bounds (drawColor for instance) then they accumulate the cull rect - which is empty if it was defaulted - which is a NOP.
Essentially I do the same thing, but at least I set a flag indicating that you had an unbounded operation with no cull rect or clip to contain it.
If I returned an infinite bounds then you'd lose any information on what was drawn that was reasonably bounded. With Skia you'd at least get the bounds of the finite operations and so I wanted to follow suit.
flow/display_list_utils.h
Outdated
|
|
||
| virtual BoundsAccumulator* accumulatorForLayer() { return outer_; } | ||
| virtual BoundsAccumulator* accumulatorForRestore() { return outer_; } | ||
| virtual SkRect getLayerBounds() { return SkRect::MakeEmpty(); } |
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.
This can be pure virtual right? Can you also document these virtuals? And also OOL them?
Making them pure virtual may make the subclasses slightly easier to read. Perhaps also just document them. Your call.
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.
If they were pure virtual then I'd need another class just to implement the most common trivial implementations.
| static constexpr int kIsNonGeometric = 0x00; | ||
|
|
||
| // A geometric operation that is defined as a fill operation | ||
| // regardless of what the current paint Style is set to. | ||
| // This flag will automatically assume |kApplyMaskFilter|. | ||
| static constexpr int kIsFilledGeometry = 0x01; | ||
|
|
||
| // A geometric operation that is defined as a stroke operation | ||
| // regardless of what the current paint Style is set to. | ||
| // This flag will automatically assume |kApplyMaskFilter|. | ||
| static constexpr int kIsStrokedGeometry = 0x02; | ||
|
|
||
| // A geometric operation that may be a stroke or fill operation | ||
| // depending on the current state of the paint Style attribute. | ||
| // This flag will automatically assume |kApplyMaskFilter|. | ||
| static constexpr int kIsDrawnGeometry = 0x04; | ||
|
|
||
| static constexpr int kIsAnyGeometryMask = // | ||
| kIsFilledGeometry | // | ||
| kIsStrokedGeometry | // | ||
| kIsDrawnGeometry; |
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.
I like the idea of using an enum if we don't want to make it an enum class which would need overloading the operators. Just groups the various values better IMO.
enum FlagValue {
kSomething = 1 << 0,
kSomethingElse = 1 << 1,
kAnotherThing = 1 << 2,
};
|
Still TBD - a better name for "transparentOccluder" that doesn't parse as "the thing, except its not the thing because it has a flaw". (I added this to the issues mentioned in the Flutter Issue below. The name, for now, at least matches what Skia and PhysicalShapeLayer call the parameter...) I am not planning on switching the flags to enums at this point. Naming style issues will be considered in a sweep once the functional bugs are all fixed. See flutter/flutter#90232 |
chinmaygarde
left a comment
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.
Looks good to me. Are there any open questions from other reviewers to be handled here?
@dnfield ? |
|
It looks like dnfield cannot get back to this today. I don't think he expressed any functional concerns (other than the GPU lifetime concerns that were already patched by another PR) and I believe that the code style concerns he expressed can be dealt with under flutter/flutter#90232 so I'm going to land this and can address any remaining concerns with future PRs. |
These changes are to help improve our bounds calculations - and thus our cache memory usage - for the DisplayList.
Fixes #28656
The effort was triggered by the regression documented in flutter/flutter#88745 which resulted in disabling the DisplayList mechanism by default in the product. Rerunning the tests confirms that this PR fixes flutter/flutter#88745
Testing locally with the Flutter Gallery transition benchmarks we could see that our cache usage was higher with DisplayList and we've taken a number of steps to reduce that usage back to the baseline, including this PR and #28158 and #28287.
Improving our bounds resulted in a whole new algorithm for the
DisplayListBoundsCalculatorclass which now does all of its own work except for a few cases where we deal with opaque Skia objects which must be trusted as sources or modifiers of rendering bounds (SkTextBlob, ShadowUtils, SkImageFilter, SkMaskFilter, SkPathEffect). Some reasons for introducing the new architecture were:For this PR the bounds accuracy testing in
display_list_canvas_unittests.cchave been enabled by default and they now verify what should be reasonably optimal bounds representing a tradeoff between time and area. More importantly, there is an assert that our bounds are never larger than the bounds computed by SkPicture/Recorder for the tested cases which are fairly extensive, representing:The matrix of tests is quite large and the bounds returned are both encompassing of all pixels that were rendered (or else a hard test failure results) and no looser than those computed by the Skia mechanism (which, for now, just reports the condition as it does not represent a functional program bug). (Note that our bounds accuracy performance is already being tracked by the recent changes to report cache metrics for all benchmarks to Skia Perf so we should get perf failures if our cache usage regresses.)
The tests were modified in a couple of key ways to try to verify these conditions:
ahemfont still produces wildly over-estimated bounds for a simple string. So, a tolerance for the base bounds of a text blob are recorded.A minor, seemingly unrelated, change in the code was made to rename one of the parameters to drawShadow which had a name that conflicted with its usage. It shouldn't have been causing problems because it was just passed along from source to destination as "that flag that the developer handed us and which we need to remember and eventually pass to Skia". Since we never interpreted its value ourselves, the name didn't really matter, but I noticed it was wrong when I was updating the shadow tests.