-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] Defer drawable aquisition until root pass encoding. #48141
Conversation
| .GetMTLTexture(); | ||
| } | ||
|
|
||
| // Render command encoder creation has been observed to exceed the stack |
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.
TODO: need to make this a regular NSThread so I can increase the stack size.
|
I played with this branch a bit on iPhone 13 PRO, 120hz. Haven't gotten to partial repaint yet. it is probably a good thing to not block raster thread when waiting for drawable. That said, I measured how much it takes to obtain the drawable in For Flutter driven animations, such as activity indicator, It gets messier with shorter animations started from user interaction, like ballistic or material spill. Less consistency there. Can't shake off the feeling that we have a timing issue somewhere. |
|
After disabling |
|
Well, if there is a timing issue on our side I can't seem to find what it is. Also apple official example seems to have same problem - when rendering on background thread, So I do have a bit of concern about this PR - it removes the lag from raster thread, which is good, but given that |
|
presentsWithTransaction from the background thread isn't valid, we went down that path before: flutter/flutter#133147 |
|
Also I don't see how we would drop frames. If you try and run an application with very slow rendering (say dozens of blurs), then we still get backpressure. |
Even with this PR sometimes |
|
If we're blocking on aquiring the drawable we're already commited to that frame, I don't see how you would drop it? |
Not me, the compositor - If When playing with this branch I noticed that when this happens I get display link callbacks while |
but we're not dropping that presentation. its going to happen at the next vsync interval. There is no indication I've seen that a drawable has a specific presentation time it must be submited by or it gets dropped, especially if there is no CA transaction associated with the presentation. |
|
And again, why would this be worse than blocking for 7-8ms before we do any raster work? |
Right. But we don't know that we missed the deadline. So there's already another frame being rasterized and if that happens fast enough it will be presented before the end of next vsync. Thus the previous frame will dropped by the compositor. When this happens it will not be shown as jank on the timeline, but it will seem janky on device.
It's definitely not worse. |
|
I don't want to sound too negative. Unless we can figure out what causes the delay (not holding my breath) this is a clear improvement. |
|
The scenario you are describing is already technically possible today. We can get up to a pipeline depth of two, which means that from the engine's perspective of a vsync interval, double submission is entirely possible. This was my working theory for a while, but I can't find any evidence that this is actually happening on the system side. drawables have a presentation time attribute that we could intrument to determine if any are getting skipped, I can try that today.
I don't think you're being too negative. its important to be critical on changes like this, especially those that rely on APple's under documented functionality. |
|
Here is how I am testing this. Using an app that just has a linear progress indicator indeterminatly spinning. The documentation for addPresentedHandler is unclear as to whether or not it fires if a drawable was skipped. It does note that drawable.presentedTime will be 0 if the drawable is skipped, which makes me thing it will. To ensure this is the case, I add a static atomic int, increment it for acuqired drawables and decrement it when the present handler fires. If we were skipping drawables and the handler fires, I'll see a print. If skipping drawables doesn't cause the handler to fire, then the number will increment over time. I don't have any expectations for what the value is itself since the docs dont say when the presented handler is fired, but I'd suspect that 3 or less would be reasonable. diff --git a/impeller/renderer/backend/metal/surface_mtl.mm b/impeller/renderer/backend/metal/surface_mtl.mm
index 4a49692c5f..5c9d502725 100644
--- a/impeller/renderer/backend/metal/surface_mtl.mm
+++ b/impeller/renderer/backend/metal/surface_mtl.mm
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "impeller/renderer/backend/metal/surface_mtl.h"
+#include <atomic>
#include <optional>
#include "flutter/fml/trace_event.h"
@@ -18,6 +19,8 @@
namespace impeller {
+static std::atomic_int64_t present_count_ = 0;
+
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunguarded-availability-new"
@@ -307,8 +310,17 @@ bool SurfaceMTL::Present() const {
[drawable present];
} else {
auto deferred_drawable = deferred_drawable_;
+ present_count_++;
[command_buffer addScheduledHandler:^(id<MTLCommandBuffer> _Nonnull) {
auto drawable = deferred_drawable->get();
+ [drawable addPresentedHandler:^(id<MTLDrawable> draw){
+ present_count_--;
+ // Returns 0 if a frame has not been presented or has been skipped.
+ if (draw.presentedTime == 0) {
+ FML_LOG(ERROR) << "SKIPPED";
+ }
+ FML_LOG(ERROR) << present_count_;
+ }];
[drawable present];
}];
[command_buffer commit];If I run this then I observe the number very slowly creeping up from 2 to 3. If I let it run for multiple minutes that doesn't seem to change. If we were regularly dropping presentation I would expect to see this number climb quickly. If it happened only rarely then it would climb slowly. It seems consistent. If I background the app, then I get a bunch of. "SKIPPED", I suspect because iOS pauses rendering. THe callback seems to fire for every cmd buffer. If I foreground it again, it goes right back to 2-3. |
|
Trying a different app like https://github.com/gskinnerTeam/flutter-wonderous-app/ , I notice a similar behavior expect the count gets higher. Oddly enough I can consistently generate a skipped frame on the 2nd or 3rd frame after I start a slow gesture to swipe between the tabs. I'm going to try blocking on the cmd buffer to see if that reproduces in all cases or is specific to using the worker thread. |
|
Even if I block on drawable aquisition on the raster thread instead of in the worker like I'm doing here, I still see iOS skipping drawables when I initiaiate a gesture on the wonderous screen. This is with waitForNextDrawable on raster thread blocking, but at end of raster workload: |
|
If this was because we use present and not presentDrawable:atTime: or presentDrawable:afterMinimumDuration: I wouldn't expect to see this on the first few frames of a gesture. |
|
Its actually very easy to reproduce by putting your thumb down on the screen and lifting up without moving. |
|
I think I finally have a clue about what is going on. I have a simple metal application with one displaylink on background thread (slightly modified example). Even with this fairly simple example, iPhone 13 Pro will sometimes not manage to put frame on glass in expected time. The interesting part is the behavior when this happens:
I tried a simple experiment, every time TLDR: If we move |
|
To confirm my suspicion that without backpressure on raster thread and not dropping frames this PR will keep increasing presentation latency ( Note that this is on A15. Maybe on faster devices more frames will be presented on time and the latency won't increase that quickly. Here's what 200ms presentation latency after few minutes of animation looks like :) latency.mov |
|
Considering all of the above, here is a very quick prototype for frame dropping. It makes some assumptions about frame duration, but it seems to be working quite well. Circular progress indicator example with frame dropping:
Same thing on main:
In this example, only one frame gets dropped at the beginning of transition from gallery menu to example. That is enough to remove the delay from |
|
Really interesting results - I think we can keep the backpressure if it provides some mechanism to avoid getting out of sync. But the idea of dropping frames ourselves is absolutely terrifying to me. i.e. we drop the last frame of a particular animation we could leave the UI in an inconsistent state, right? I suspect that the presentation engine has more herustics than we do. So if its the case that presentsWIthTransaction solves this problem by allowing the presentation engine to drop frames, then it sounds like we actually need to merge the raster thread onto the platform thread so we can safely present with transaction. |
I don't think this is about heuristics. As far as I can tell, As for dropping the last frame, if you look at my prototype branch, that should be a fixable problem.
I think that would be a last resort? |
|
Alternatively we can investigate a frame dropping approach - it worries me greatly tbh, I don't know how we make that safe. |
|
I've added back the backpressure mechanism to this PR. |
It should worry you :) But that doesn't mean it's not worth giving it a shot. |
|
lol fair. I am worried a lot in general these days so I'm probably being overly cautious. I think we should be able to tell if there is another frame in flight (since flutter will render with a pipeline depth of 2). That would be a signal that is safe to skip. I would need to think about the edge cases there. |
|
Closing for now, I think @knopp 's changes will be the preferred path (once we do some more testing). |



WIP. replaces #47976