-
Notifications
You must be signed in to change notification settings - Fork 6k
Set SkPath::setIsVolatile based on whether the path survives at least two frames #22620
Conversation
|
I just realized I don't have any tests checked in for this yet. I'll get to that after thanksgiving. |
lib/ui/painting/path.cc
Outdated
|
|
||
| int CanvasPath::getFillType() { | ||
| return static_cast<int>(path_.getFillType()); | ||
| return static_cast<int>(tracked_path_->path_.getFillType()); |
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.
Use path() to access the SkPath in these methods. Add a private non-const mutable_path() accessor for methods that modify the path.
lib/ui/painting/path.h
Outdated
| SkPath path_; | ||
| static constexpr int number_of_frames_until_non_volatile = 2; | ||
| static std::mutex volatile_paths_mutex_; | ||
| static std::unordered_set<std::shared_ptr<TrackedVolatilePath>> |
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 this be moved to an object that lives at Shell scope and can be accessed through the UIDataState?
(similar to something like IOManager)
That would have some advantages:
- no need for a mutex if this object is only accessed on the UI thread
- if multiple engine instances exist, then one engine's NotifyIdle will not affect other engines' paths
- paths in the set will be cleaned up when the shell is deleted
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.
That SGTM, but I think we'd still need the mutex because the erase call could come from a Dart VM worker thread.
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.
Although I suppose we could just do a post task for it...
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.
The problem with this will be we don't know if we're in isolate scope or not when a path gets collected.
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.
Ok - I made this more or less work by sharing the task runner and just grabbing out the tracker when we create the path, at wihch point we'll always have isolate scope.
I think this should be correct.
|
|
||
| namespace flutter { | ||
|
|
||
| class VolatilePathTracker { |
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.
Add documentations for public classes.
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.
Done
lib/ui/volatile_path_tracker.h
Outdated
|
|
||
| // Starts tracking a path. | ||
| // Must be called from the UI task runner. | ||
| void insert(std::shared_ptr<Path> path); |
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.
Flutter engine uses Google C++ naming style https://google.github.io/styleguide/cppguide.html#Function_Names so Insert might be better.
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.
Done
lib/ui/painting/path.cc
Outdated
| CanvasPath::~CanvasPath() {} | ||
| void CanvasPath::ReleaseDartWrappableReference() const { | ||
| FML_DCHECK(path_tracker_); | ||
| path_tracker_->erase(tracked_path_); |
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 do the following?
if (tracked_path_->tracking_volatility) {
path_tracker_->erase(tracked_path_);
}
This seems to be able to save some fml::TaskRunner::RunNowOrPostTask calls when the path is non-volatile.
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.
Nice, done
|
|
||
| CanvasPath::~CanvasPath() = default; | ||
|
|
||
| void CanvasPath::resetVolatility() { |
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.
The name resetVolatility sounds like it will also reset the volatility frame count but that does not seem to be the case. I believe resetting the count is the intended behavior as it's called in those methods that mutate the path.
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.
Done
lib/ui/painting/path.cc
Outdated
| auto other_mutable_path = path->mutable_path(); | ||
| mutable_path().transform(ToSkMatrix(matrix4), &other_mutable_path); | ||
| matrix4.Release(); | ||
| resetVolatility(); |
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 method does not seem to mutate the current path. Can we do the following?
path().transform(ToSkMatrix(matrix4), &other_mutable_path);
matrix4.Release(); matrix4.Release();
// resetVolatility() is not needed.
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.
Ahh it mutates the new path, got it. Done.
lib/ui/painting/path.h
Outdated
| const SkPath& src) { | ||
| fml::RefPtr<CanvasPath> path = CanvasPath::Create(path_handle); | ||
| path->path_ = src; | ||
| path->tracked_path_->path_ = src; |
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: path->tracked_path_->path_ -> path->mutable_path()
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.
Done.
lib/ui/volatile_path_tracker.cc
Outdated
| [&]() { paths_.erase(path); }); | ||
| } | ||
|
|
||
| void VolatilePathTracker::onFrame() { |
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.
It might be nice to add some tracing here. For example, how many paths are tracked, how many survived, and how many are marked as non-volatile.
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.
Added some arguments.
lib/ui/volatile_path_tracker.cc
Outdated
|
|
||
| void VolatilePathTracker::erase(std::shared_ptr<Path> path) { | ||
| FML_DCHECK(path); | ||
| fml::TaskRunner::RunNowOrPostTask(ui_task_runner_, |
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 wonder if fml::TaskRunner::RunNowOrPostTask's overhead could be significant here. Can we add a benchmark that inserts/erases 10,000 simple paths (e.g., tiny rects) per frame?
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 will get called from whatever thread Dart is doing GC on. In local testing, that's working out to be a random worker thread from the VM.
I'm not sure what it is you'd like to benchmark - speed of erasure when a different thread is actually used?
The only other option I can think of for this is to use a mutex to guard the collection, but that should be slower than this method since it would need to acquire the lock in all three methods rather than just jumping the thread in this one.
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.
Another possibility would be a batching mechanism that schedules a deletion task with a delay and deletes all paths that were accumulated during the delay (similar to how SkiaUnrefQueue works).
How many paths are created while rendering an animation? Are most of these paths typically GCed around the same time?
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 the rive example app, I'm seeing about 100 paths per frame. It's not entirely clear to me about the timing of the GC.
I'll look at writing a benchmark for this.
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 wrote a benchmark.
I went with this strategy:
Erase()now adds paths to a separate collection, guarded by a mutexOnFrame()callsDrain()before processing pathsDrain()checks whether that collection needs to be drained. If so, it swaps it with an empty collection using the mutex, and then removes those paths from the set of paths we're tracking.- OnFrame goes on to process any remaining paths.
The benchmark is now silly fast for this, whereas before it was relatively slow.
runtime/runtime_controller.h
Outdated
| /// data that the root isolate can | ||
| /// access in a synchronous manner. | ||
| /// @param[in] volatile_path_tracker Cache for tracking path | ||
| /// volatility. |
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.
Format problem.
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.
Fixed
runtime/dart_isolate.h
Outdated
| std::string advisory_script_entrypoint, | ||
| bool is_root_isolate); | ||
| bool is_root_isolate, | ||
| std::shared_ptr<VolatilePathTracker> volatile_path_tracker_); |
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.
Remove the trailing underscore on volatile_path_tracker_
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.
Oops.
lib/ui/volatile_path_tracker.cc
Outdated
| for (auto path : paths_to_remove) { | ||
| paths_.erase(path); | ||
| } | ||
| needs_drain_ = false; |
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.
Do the writes to needs_drain_ while holding the paths_to_remove_mutex_ (this also applies to VolatilePathTracker::Erase)
This will ensure that needs_drain_ is always in sync with the emptiness of paths_to_remove_ and that a needs_drain_ = false write here does not overwrite a true value set by VolatilePathTracker::Erase
|
This is failing to render the FAB buttin in our hello world app. Looking into why. |
|
Also, the unit test, as written, is flaky in non-debug mode. |
|
LWE failure is flutter/flutter#71581. Rerunning. |
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.
Other than the thread safety of the tracking_volatility flag and issues around mutation during enumeration, everything else is nits.
lib/ui/painting/path_unittests.cc
Outdated
| UIDartState::Current()->GetVolatilePathTracker(); | ||
| EXPECT_TRUE(tracker); | ||
|
|
||
| EXPECT_TRUE(path->path().isVolatile()); |
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.
Minor Nit: This test is brittle to changes to the value of VolatilePathTracker::kFramesOfVolatility. Maybe perform that many checks for false before the eventual true assertion.
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.
Will fix.
lib/ui/fixtures/ui_test.dart
Outdated
| Future<void> _onBeginFrameDone() native 'OnBeginFrameDone'; | ||
|
|
||
| @pragma('vm:entry-point') | ||
| void create100CollectablePaths() { |
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'm confused. This seems dead code. Did you forget to check in the test maybe?
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 forgot to remove this code when I changed how a test worked.
lib/ui/painting/path_unittests.cc
Outdated
| namespace flutter { | ||
| namespace testing { | ||
|
|
||
| TEST_F(ShellTest, PathVolatilityTracking) { |
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.
Feel free to be a bit more verbose with names of test cases. PathVolatilityOldPathsBecomeNonVolatile and PathVolatilityGCRemovesPathFromTracker
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.
Done
| void VolatilePathTracker::Insert(std::shared_ptr<Path> path) { | ||
| FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); | ||
| FML_DCHECK(path); | ||
| FML_DCHECK(path->path.isVolatile()); |
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.
Should this component care if the path is already volatile? You could just drop it on the floor and move on. I don't see it documented that the path must be non-volatile.
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.
It just seems like an error to add a path that's already been marked as non-volatile, and makes for slightly more work/memory usage. I can update the doc.
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.
Sounds good.
| void VolatilePathTracker::OnFrame() { | ||
| FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); | ||
| std::string total_count = std::to_string(paths_.size()); | ||
| TRACE_EVENT1("flutter", "VolatilePathTracker::OnFrame", "count", |
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: The string-ification of trace counts is done automatically for you by the macros. So paths_.size() can go directly in total_count.c_str()
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.
Ditto elsewhere.
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'm missing something then - TraceArg is defined as const char*. paths_.size() is size_type. If I use paths_.size() it's a compile time error.
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.
Ok, I guess that is FML_TRACE_EVENT which are the new and easier to use namespaced variants of these trace macros. These should use the arithmetic specialization of TraceToString. I guess we should get to this TODO.
lib/ui/volatile_path_tracker.cc
Outdated
|
|
||
| Drain(); | ||
|
|
||
| for (auto it = paths_.begin(), last = paths_.end(); it != last;) { |
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.
It really easy to introduce bugs because of mutation while enumeration. Also, it's really hard to reason about.
Here, I am still not sure if this is fully safe. First, I believe you are getting away with modifying the path because the unordered set hash function is looking at the pointer to the path. So I guess thats safe but not immediately clear. Second, I am not sure if the iterator returned by erase is the same as that returned by it++ if the erase was not necessary. Thats because unordered set iteration does not guarantee any stable order. So, depending on the implementation, I am not sure if you will end up skipping over some paths in the drain.
I suggest something way simpler:
- Iterate over all paths like normal and add the paths to keep in a separate set.
- Swap that set with
paths_after.
I believe that also has better algorithmic complexity. You also don't need unordered_set so you'll have better cache locality with std::set. Of course, the perf aspects aside, my main motivation for the suggestion is that it's just easier to read and bulletproof against mutation while enumeration concerns 😄
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.
unordered_set::erase ensures that iterators (other than the erased iterator) remain valid and the iteration order is unchanged (see https://en.cppreference.com/w/cpp/container/unordered_set/erase)
unordered_set iterators are const, so the elements in the set can not be mutated during iteration. It would be clearer if path is explictly declared as a const std::shared_ptr<Path> instead of using auto.
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've updated to be explicit that the shared_ptr is const.
I'm ambivalent here. This should be a pretty safe way to iterate and erase in one pass without additional space overhead. This collection shouldn't typically be super large (in local testing with a rive animation, it hits a little over 100 paths). That few a number should still get helped by cache locality though. I kind of doubt doing it either way is worth the effort of writing a benchmark beyond what we'll already have for transition perf in the devicelab right now.
@chinmaygarde - if you agree this is safe, would you still rather see this refactored?
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.
unordered_set::erase ensures that iterators (other than the erased iterator) remain valid and the iteration order is unchanged (see https://en.cppreference.com/w/cpp/container/unordered_set/erase)
I read that as the validity of existing iterators. But what about the order of iteration? The result of the ++ I mean. Perhaps I am confusing this. But just using a separate set with a swap should be faster and easier to reason about right?
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'll go ahead and refactor to use a set for now.
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.
Jason is correct. Quoting from the linked documentation: "The order of the elements that are not erased is preserved. (This makes it possible to erase individual elements while iterating through the container.)"
lib/ui/volatile_path_tracker.h
Outdated
| /// tracking a path that is no longer referenced in Dart code. | ||
| class VolatilePathTracker { | ||
| public: | ||
| struct Path { |
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: Suggest naming this TrackedPath. I keep confusing this path and CanvasPath. 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.
Done
lib/ui/painting/path.cc
Outdated
|
|
||
| void CanvasPath::ReleaseDartWrappableReference() const { | ||
| FML_DCHECK(path_tracker_); | ||
| if (tracked_path_->tracking_volatility) { |
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 happen on any thread right? For example, the thread on which the GC happens. In that case, isn't there a data race between this and the the setter in resetVolatility? Maybe you are depending on the fact that once the Dart VM releases the reference to the path, we won't be in a situation where we will call reset volatility. But I am not sure that we can guarantee that case since we could still be holding onto the shared pointer on the native side.
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.
Just tracking volatility from the get go in the constructor and not needing the flag (and its unsafe check) should make it so that you don't need the check.
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 believe this would be safe if the tracking_volatility flag was made an atomic<bool>.
This relies on the contract that tracking_volatility will only be set to true by resetVolatility, which only happens during Dart->native method calls that run on the UI thread. When ReleaseDartWrappableReference is called, no such method calls can be active because the Dart object is being GCed.
There is a race against VolatilePathTracker::OnFrame which could be running concurrently on the UI thread and might clear tracking_volatility while removing the path from the tracker. However, the worst case is that ReleaseDartWrappableReference does an unnecessaryErase of a path that was already removed. If that happens Erase/Drain will just call unordered_set::erase for that path which will do nothing.
However, this is confusing and makes it easy to introduce errors.
The advantage of the check is avoiding the performance cost of the Erase (which might take a lock) and the later Drain if the GCed path is not tracked.
If the cost is small, another alternative would be to always call Erase in ReleaseDartWrappableReference and avoid reading tracking_volatility on the GC thread. Drain on the UI thread could then safely check tracking_volatility in order to avoid doing the unordered_set::erase if the path is not tracked.
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.
So my thought is that at best, this will save us taking a lock, and at worst we'll get a data race that causes us to insert this into the collection even though the path has been GC'd, and it will be removed anyway.
I guess the risk is that some refactor happens which changes that worst case scenario to something bad.
@chinmaygarde - WDYT about making it an atomic<bool> and documenting this expectation?
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.
no such method calls can be active because the Dart object is being GCed.
I was thinking of calls to CanvasPath::Create from the native side. But I suppose that doesn't happen today. I may be overthinking cases where these objects are created from the native side.
If the cost is small, another alternative would be to always call Erase in ReleaseDartWrappableReference and avoid reading tracking_volatility on the GC thread.
IMO, that would be a lot clearer.
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.
SG, done. We can always come back to this later with a benchmark if it makes a difference.
lib/ui/volatile_path_tracker.h
Outdated
| /// tracking a path that is no longer referenced in Dart code. | ||
| class VolatilePathTracker { | ||
| public: | ||
| struct Path { |
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'd add a docstring statement here saying that fields of this struct will only be accessed on the UI task runner (see the note about the access of one of the fields in the ReleaseDartWrappable callback).
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'll do that on resolution of whether using an atomic and documenting it is worthwhile here.
| void CanvasPath::resetVolatility() { | ||
| if (!tracked_path_->tracking_volatility) { | ||
| mutable_path().setIsVolatile(true); | ||
| tracked_path_->frame_count = 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.
Seems redundant.
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 this path had previously been marked as non-volatile, but now has been mutated and is volatile again, we have to reset the frame count so that we don't immediately mark it volatile if it is only drawn for e.g. 1 frame.
…at least two frames (flutter/engine#22620)
* 3a30ae3 Fix ios voiceover (for safari >13.4) (flutter/engine#22965) * 4338849 Replace g_object_weak_ref with g_object_add_weak_pointer * 3b9937a Load macOS dart bundle by URL fallback (flutter/engine#22979) * 96927bb add ffi_struct_patch.dart to libraries.yaml (flutter/engine#23000) * 2efc7c1 Set SkPath::setIsVolatile based on whether the path survives at least two frames (flutter/engine#22620) * bb81b95 Allow Tile mode for blur filter and add new decal TileMode (flutter/engine#22982) * 9df2157 Load iOS dart bundle by URL fallback (flutter/engine#22997) * 7647fdb Roll Skia from 22f80a60b17f to 6b07e0eb497c (26 revisions) (flutter/engine#23005) * 062cbd8 Freiling warmup memory (flutter/engine#22984) * 1646966 Revert "Freiling warmup memory (#22984)" (flutter/engine#23007) * 50d830a [web] Do not reset 'cursor' in PersistedPlatformView. (flutter/engine#22977) * 6ebf5c3 Roll Dart SDK from e4c9b06267d3 to a4e6fe145bf7 (2 revisions) (flutter/engine#23006) * 14c8c24 [web] Fix regression in foreground style (flutter/engine#22999) * 6678efa Implement SystemSound.play * fb769a4 Roll Fuchsia Linux SDK from rnN_X2o75... to ESzmO-yOF... (flutter/engine#23010) * b424356 Roll Skia from 6b07e0eb497c to f7cce2b243b2 (6 revisions) (flutter/engine#23018) * 56035c7 Roll Fuchsia Linux SDK from ESzmO-yOF... to K4cPd0-Xd... (flutter/engine#23020) * cb4a2ef Roll Skia from f7cce2b243b2 to b0cb8372c1ef (3 revisions) (flutter/engine#23021) * cc8c9d4 Roll Skia from b0cb8372c1ef to 5284e96599a8 (2 revisions) (flutter/engine#23023) * 8e9a943 Roll Dart SDK from a4e6fe145bf7 to c287db6bf232 (2 revisions) (flutter/engine#23024) * 714b543 Roll Fuchsia Mac SDK from OUQEzH1oE... to a9yuHfriB... (flutter/engine#23025) * d50cdda Roll Dart SDK from c287db6bf232 to 2553a84fe438 (1 revision) (flutter/engine#23026) * 4794d04 Roll Skia from 5284e96599a8 to f7fdf1aa2911 (1 revision) (flutter/engine#23027)
…at least two frames (flutter/engine#22620)
…at least two frames (flutter/engine#22620)
…at least two frames (flutter/engine#22620)
…urvives at least two frames (flutter#22620)" (flutter#23044)" This reverts commit 4f91425.
…at least two frames (flutter/engine#22620)
* Revert "Revert "Set SkPath::setIsVolatile based on whether the path survives at least two frames (flutter#22620)" (flutter#23044)" This reverts commit 4f91425. * Fix tracing
This patch defaults the volatility bit on SkPaths to false, and then flips it to true if the path survives at least two frames.
It does this by maintaining a set of shared_ptr's to a struct containing the SkPath and some bookkeeping information about how many frames they've been around for, and removing paths from that set when we get a call to
ReleaseDartWrappableReference, which is called by the finalizer we add for the Dart VM to let us know when we're going to get collected.This results in substantial memory savings when using a Rive animation, and a slight boost in transition perf overall frame count based on local testing (see numbers in flutter/flutter#58870).
This is a less than ideal solution - it would be better if we had our own displaylist format and could track this information a bit more clearly, and just set the bit on paths whenever they actually got rendered from the displaylist. There are also planned optimizations in Skia that may make this a moot point for many of the types of paths that Rive would typically use, although this could still help in other cases where the new Skia optimization path cannot be followed.
This also avoids the need to expose the flag directly to application developers - it is a relatively confusing flag to use correctly, and it is entirely possible that an application (or the framework) won't know how to set it correctly, e.g. because some application component receives a path from another component and does not know if that same path will get drawn again elsewhere.
/cc @blasten FYI since we were discussing this earlier
/cc @brianosman @bsalomon @reed-at-google - this is a "safe" version of what we were discussing earlier today.